tabctl 0.6.0-alpha.8 → 0.6.0-alpha.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.
@@ -1,27 +1,8 @@
1
1
  (() => {
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __esm = (fn, res) => function __init() {
7
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
- };
9
3
  var __commonJS = (cb, mod) => function __require() {
10
4
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
5
  };
12
- var __export = (target, all) => {
13
- for (var name in all)
14
- __defProp(target, name, { get: all[name], enumerable: true });
15
- };
16
- var __copyProps = (to, from, except, desc) => {
17
- if (from && typeof from === "object" || typeof from === "function") {
18
- for (let key of __getOwnPropNames(from))
19
- if (!__hasOwnProp.call(to, key) && key !== except)
20
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
- }
22
- return to;
23
- };
24
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
6
 
26
7
  // dist/extension/lib/screenshot.js
27
8
  var require_screenshot = __commonJS({
@@ -40,7 +21,6 @@
40
21
  exports.getPageMetrics = getPageMetrics;
41
22
  exports.scrollToPosition = scrollToPosition;
42
23
  exports.captureTabTiles = captureTabTiles;
43
- exports.screenshotTabs = screenshotTabs;
44
24
  exports.SCREENSHOT_TILE_MAX_DIM = 2e3;
45
25
  exports.SCREENSHOT_MAX_BYTES = 2e6;
46
26
  exports.SCREENSHOT_QUALITY = 80;
@@ -185,8 +165,8 @@
185
165
  }
186
166
  return chrome.tabs.captureVisibleTab(windowId, options);
187
167
  }
188
- async function getPageMetrics(tabId, timeoutMs, deps2) {
189
- const result = await deps2.executeWithTimeout(tabId, timeoutMs, () => {
168
+ async function getPageMetrics(tabId, timeoutMs, deps) {
169
+ const result = await deps.executeWithTimeout(tabId, timeoutMs, () => {
190
170
  const doc = document.documentElement;
191
171
  const body = document.body;
192
172
  const pageWidth = Math.max(doc.scrollWidth, doc.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
@@ -206,8 +186,8 @@
206
186
  }
207
187
  return result;
208
188
  }
209
- async function scrollToPosition(tabId, timeoutMs, x, y, deps2) {
210
- const result = await deps2.executeWithTimeout(tabId, timeoutMs, (scrollX, scrollY) => {
189
+ async function scrollToPosition(tabId, timeoutMs, x, y, deps) {
190
+ const result = await deps.executeWithTimeout(tabId, timeoutMs, (scrollX, scrollY) => {
211
191
  window.scrollTo(scrollX, scrollY);
212
192
  return {
213
193
  scrollX: window.scrollX || window.pageXOffset || 0,
@@ -219,13 +199,13 @@
219
199
  }
220
200
  return result;
221
201
  }
222
- async function captureTabTiles(tab, options, deps2) {
202
+ async function captureTabTiles(tab, options, deps) {
223
203
  const tabId = tab.tabId;
224
204
  const windowId = tab.windowId;
225
205
  if (!Number.isFinite(tabId) || !Number.isFinite(windowId)) {
226
206
  throw new Error("Missing tab/window id");
227
207
  }
228
- const metrics = await getPageMetrics(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, deps2);
208
+ const metrics = await getPageMetrics(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, deps);
229
209
  if (!metrics) {
230
210
  throw new Error("Failed to read page metrics");
231
211
  }
@@ -243,7 +223,7 @@
243
223
  let tileIndex = 0;
244
224
  const captureTile = async (x, y, width, height, total) => {
245
225
  if (tileIndex > 0) {
246
- await deps2.delay(exports.SCREENSHOT_CAPTURE_DELAY_MS);
226
+ await deps.delay(exports.SCREENSHOT_CAPTURE_DELAY_MS);
247
227
  }
248
228
  const rawDataUrl = await captureVisible(windowId, options.format, options.quality);
249
229
  if (!rawDataUrl) {
@@ -278,8 +258,8 @@
278
258
  const tileCount = Math.ceil(maxX / stepX) * Math.ceil(maxY / stepY);
279
259
  for (let y = 0; y < maxY; y += stepY) {
280
260
  for (let x = 0; x < maxX; x += stepX) {
281
- await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, x, y, deps2);
282
- await deps2.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
261
+ await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, x, y, deps);
262
+ await deps.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
283
263
  const width = Math.min(stepX, maxX - x);
284
264
  const height = Math.min(stepY, maxY - y);
285
265
  try {
@@ -287,7 +267,7 @@
287
267
  } catch (err) {
288
268
  const message = err instanceof Error ? err.message : "capture_failed";
289
269
  if (message.includes("MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND")) {
290
- await deps2.delay(1e3);
270
+ await deps.delay(1e3);
291
271
  await captureTile(x, y, width, height, tileCount);
292
272
  } else {
293
273
  throw err;
@@ -295,90 +275,9 @@
295
275
  }
296
276
  }
297
277
  }
298
- await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, startScrollX, startScrollY, deps2);
278
+ await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, startScrollX, startScrollY, deps);
299
279
  return tiles;
300
280
  }
301
- async function screenshotTabs(params, requestId, deps2) {
302
- const snapshot = await deps2.getTabSnapshot();
303
- const selection = deps2.selectTabsByScope(snapshot, params);
304
- if (selection.error) {
305
- throw selection.error;
306
- }
307
- const mode = params.mode === "full" ? "full" : "viewport";
308
- const format = params.format === "jpeg" ? "jpeg" : "png";
309
- const qualityRaw = Number(params.quality);
310
- const quality = Number.isFinite(qualityRaw) ? Math.min(100, Math.max(0, Math.floor(qualityRaw))) : exports.SCREENSHOT_QUALITY;
311
- const tileMaxDimRaw = Number(params.tileMaxDim);
312
- const tileMaxDim = Number.isFinite(tileMaxDimRaw) && tileMaxDimRaw > 0 ? Math.floor(tileMaxDimRaw) : exports.SCREENSHOT_TILE_MAX_DIM;
313
- const adjustedTileMaxDim = tileMaxDim < 50 ? 50 : tileMaxDim;
314
- const maxBytesRaw = Number(params.maxBytes);
315
- const maxBytes = Number.isFinite(maxBytesRaw) && maxBytesRaw > 0 ? Math.floor(maxBytesRaw) : exports.SCREENSHOT_MAX_BYTES;
316
- const adjustedMaxBytes = maxBytes < 5e4 ? 5e4 : maxBytes;
317
- const progressEnabled = params.progress === true;
318
- const tabs2 = selection.tabs;
319
- const entries = [];
320
- let totalTiles = 0;
321
- for (let index = 0; index < tabs2.length; index += 1) {
322
- const tab = tabs2[index];
323
- const tabId = tab.tabId;
324
- const url = tab.url;
325
- if (!deps2.isScriptableUrl(url)) {
326
- entries.push({
327
- tabId,
328
- windowId: tab.windowId,
329
- groupId: tab.groupId,
330
- url: tab.url,
331
- title: tab.title,
332
- error: { message: "unsupported_url" },
333
- tiles: []
334
- });
335
- if (progressEnabled) {
336
- deps2.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs2.length, tabId });
337
- }
338
- continue;
339
- }
340
- let tiles = [];
341
- let error = null;
342
- try {
343
- const windowId = tab.windowId;
344
- const activeTabs = await chrome.tabs.query({ windowId, active: true });
345
- const activeTabId = activeTabs[0]?.id ?? null;
346
- if (activeTabId && activeTabId !== tabId) {
347
- await chrome.tabs.update(tabId, { active: true });
348
- await deps2.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
349
- }
350
- try {
351
- await deps2.waitForTabReady(tabId, params, exports.SCREENSHOT_PROCESS_TIMEOUT_MS);
352
- tiles = await captureTabTiles(tab, { mode, format, quality, tileMaxDim: adjustedTileMaxDim, maxBytes: adjustedMaxBytes }, deps2);
353
- } finally {
354
- if (activeTabId && activeTabId !== tabId) {
355
- await chrome.tabs.update(activeTabId, { active: true });
356
- }
357
- }
358
- } catch (err) {
359
- const message = err instanceof Error ? err.message : "capture_failed";
360
- error = { message };
361
- }
362
- totalTiles += tiles.length;
363
- entries.push({
364
- tabId: tab.tabId,
365
- windowId: tab.windowId,
366
- groupId: tab.groupId,
367
- url: tab.url,
368
- title: tab.title,
369
- tiles,
370
- ...error ? { error } : {}
371
- });
372
- if (progressEnabled) {
373
- deps2.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs2.length, tabId });
374
- }
375
- }
376
- const response = {
377
- totals: { tabs: tabs2.length, tiles: totalTiles },
378
- entries
379
- };
380
- return response;
381
- }
382
281
  }
383
282
  });
384
283
 
@@ -387,21 +286,10 @@
387
286
  "dist/extension/lib/content.js"(exports) {
388
287
  "use strict";
389
288
  Object.defineProperty(exports, "__esModule", { value: true });
390
- exports.SETTLE_POLL_INTERVAL_MS = exports.SETTLE_STABILITY_MS = void 0;
391
- exports.isScriptableUrl = isScriptableUrl2;
392
289
  exports.delay = delay2;
393
290
  exports.executeWithTimeout = executeWithTimeout2;
394
- exports.extractPageMeta = extractPageMeta2;
395
- exports.extractSelectorSignal = extractSelectorSignal2;
396
- exports.waitForTabLoad = waitForTabLoad2;
397
- exports.waitForDomReady = waitForDomReady2;
398
- exports.waitForSettle = waitForSettle2;
399
- exports.waitForTabReady = waitForTabReady2;
400
- exports.SETTLE_STABILITY_MS = 500;
401
- exports.SETTLE_POLL_INTERVAL_MS = 50;
402
- function isScriptableUrl2(url) {
403
- return typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"));
404
- }
291
+ exports.extractPageMeta = extractPageMeta;
292
+ exports.extractSelectorSignal = extractSelectorSignal;
405
293
  function delay2(ms) {
406
294
  return new Promise((resolve) => setTimeout(resolve, ms));
407
295
  }
@@ -428,7 +316,7 @@
428
316
  return null;
429
317
  }
430
318
  }
431
- async function extractPageMeta2(tabId, timeoutMs, descriptionMaxLength) {
319
+ async function extractPageMeta(tabId, timeoutMs, descriptionMaxLength) {
432
320
  const result = await executeWithTimeout2(tabId, timeoutMs, () => {
433
321
  const pickContent = (selector) => {
434
322
  const el = document.querySelector(selector);
@@ -455,7 +343,7 @@
455
343
  h1: (meta.h1 || "").slice(0, descriptionMaxLength)
456
344
  };
457
345
  }
458
- async function extractSelectorSignal2(tabId, specs, timeoutMs, selectorValueMaxLength) {
346
+ async function extractSelectorSignal(tabId, specs, timeoutMs, selectorValueMaxLength) {
459
347
  if (!specs.length) {
460
348
  return null;
461
349
  }
@@ -558,2951 +446,103 @@
558
446
  }
559
447
  return result;
560
448
  }
561
- function waitForTabLoad2(tabId, timeoutMs) {
562
- return new Promise((resolve) => {
563
- let settled = false;
564
- const done = () => {
565
- if (settled) {
566
- return;
567
- }
568
- settled = true;
569
- chrome.tabs.onUpdated.removeListener(onUpdated);
570
- resolve();
571
- };
572
- const onUpdated = (updatedTabId, info) => {
573
- if (updatedTabId === tabId && info.status === "complete") {
574
- done();
575
- }
576
- };
577
- chrome.tabs.onUpdated.addListener(onUpdated);
578
- chrome.tabs.get(tabId).then((tab) => {
579
- if (tab.status === "complete") {
580
- done();
581
- }
582
- }).catch(() => {
583
- done();
584
- });
585
- setTimeout(done, timeoutMs);
586
- });
587
- }
588
- async function waitForDomReady2(tabId, timeoutMs) {
589
- const result = await executeWithTimeout2(tabId, timeoutMs, () => {
590
- if (document.readyState === "interactive" || document.readyState === "complete") {
591
- return true;
592
- }
593
- return new Promise((resolve) => {
594
- const onReady = () => {
595
- document.removeEventListener("DOMContentLoaded", onReady);
596
- resolve(true);
597
- };
598
- document.addEventListener("DOMContentLoaded", onReady, { once: true });
599
- setTimeout(() => {
600
- document.removeEventListener("DOMContentLoaded", onReady);
601
- resolve(false);
602
- }, Math.max(0, timeoutMs - 50));
603
- });
604
- });
605
- if (result === null) {
606
- await delay2(Math.min(200, Math.max(50, Math.floor(timeoutMs / 10))));
607
- }
608
- }
609
- async function waitForSettle2(tabId, timeoutMs) {
610
- const startTime = Date.now();
611
- let lastUrl = "";
612
- let lastTitle = "";
613
- let stableStart = Date.now();
614
- while (Date.now() - startTime < timeoutMs) {
615
- const tab = await chrome.tabs.get(tabId).catch(() => null);
616
- if (!tab)
617
- return;
618
- const currentUrl = tab.url || "";
619
- const currentTitle = tab.title || "";
620
- if (currentUrl !== lastUrl || currentTitle !== lastTitle) {
621
- lastUrl = currentUrl;
622
- lastTitle = currentTitle;
623
- stableStart = Date.now();
624
- } else if (isScriptableUrl2(currentUrl) && tab.status === "complete" && Date.now() - stableStart >= exports.SETTLE_STABILITY_MS) {
625
- return;
626
- }
627
- await delay2(exports.SETTLE_POLL_INTERVAL_MS);
628
- }
629
- }
630
- async function waitForTabReady2(tabId, params, fallbackTimeoutMs) {
631
- const waitFor = typeof params.waitFor === "string" ? params.waitFor.trim().toLowerCase() : "";
632
- if (!waitFor || waitFor === "none") {
633
- return;
634
- }
635
- const timeoutRaw = Number(params.waitTimeoutMs);
636
- const timeoutMs = Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.floor(timeoutRaw) : fallbackTimeoutMs;
637
- if (waitFor === "settle") {
638
- await waitForSettle2(tabId, timeoutMs);
639
- return;
640
- }
641
- try {
642
- const tab = await chrome.tabs.get(tabId);
643
- if (!isScriptableUrl2(tab.url)) {
644
- return;
645
- }
646
- } catch {
647
- return;
648
- }
649
- if (waitFor === "load") {
650
- await waitForTabLoad2(tabId, timeoutMs);
651
- return;
652
- }
653
- if (waitFor === "dom") {
654
- await waitForDomReady2(tabId, timeoutMs);
655
- }
656
- }
657
- }
658
- });
659
-
660
- // dist/extension/lib/groups.js
661
- var require_groups = __commonJS({
662
- "dist/extension/lib/groups.js"(exports) {
663
- "use strict";
664
- Object.defineProperty(exports, "__esModule", { value: true });
665
- exports.getGroupTabs = getGroupTabs;
666
- exports.listGroupSummaries = listGroupSummaries;
667
- exports.summarizeGroupMatch = summarizeGroupMatch;
668
- exports.findGroupMatches = findGroupMatches;
669
- exports.resolveGroupByTitle = resolveGroupByTitle2;
670
- exports.resolveGroupById = resolveGroupById2;
671
- exports.listGroups = listGroups2;
672
- exports.groupUpdate = groupUpdate2;
673
- exports.groupUngroup = groupUngroup2;
674
- exports.groupAssign = groupAssign2;
675
- exports.groupGather = groupGather2;
676
- function getGroupTabs(windowSnapshot, groupId) {
677
- return windowSnapshot.tabs.filter((tab) => tab.groupId === groupId).sort((a, b) => {
678
- const ai = Number(a.index);
679
- const bi = Number(b.index);
680
- return (Number.isFinite(ai) ? ai : 0) - (Number.isFinite(bi) ? bi : 0);
681
- });
682
- }
683
- function listGroupSummaries(snapshot, buildWindowLabels2, windowId) {
684
- const windowLabels = buildWindowLabels2(snapshot);
685
- const summaries = [];
686
- const windows = snapshot.windows;
687
- for (const win of windows) {
688
- if (windowId && win.windowId !== windowId) {
689
- continue;
690
- }
691
- for (const group of win.groups) {
692
- summaries.push({
693
- windowId: win.windowId,
694
- windowLabel: windowLabels.get(win.windowId) ?? null,
695
- groupId: group.groupId,
696
- title: typeof group.title === "string" ? group.title : null
697
- });
698
- }
699
- }
700
- return summaries;
701
- }
702
- function summarizeGroupMatch(match, windowLabels) {
703
- return {
704
- windowId: match.windowId,
705
- windowLabel: windowLabels.get(match.windowId) ?? null,
706
- groupId: match.group.groupId,
707
- title: typeof match.group.title === "string" ? match.group.title : null
708
- };
709
- }
710
- function findGroupMatches(snapshot, groupTitle, windowId) {
711
- const matches = [];
712
- const windows = snapshot.windows;
713
- for (const win of windows) {
714
- if (windowId && win.windowId !== windowId) {
715
- continue;
716
- }
717
- for (const group of win.groups) {
718
- if (group.title === groupTitle) {
719
- matches.push({
720
- windowId: win.windowId,
721
- group,
722
- tabs: getGroupTabs(win, group.groupId)
723
- });
724
- }
725
- }
726
- }
727
- return matches;
728
- }
729
- function resolveGroupByTitle2(snapshot, buildWindowLabels2, groupTitle, windowId) {
730
- const windowLabels = buildWindowLabels2(snapshot);
731
- const allMatches = findGroupMatches(snapshot, groupTitle);
732
- const matches = windowId ? allMatches.filter((match) => match.windowId === windowId) : allMatches;
733
- const availableGroups = listGroupSummaries(snapshot, buildWindowLabels2);
734
- if (matches.length === 0) {
735
- const message = windowId && allMatches.length > 0 ? "Group title not found in specified window" : "No matching group title found";
736
- return {
737
- error: {
738
- message,
739
- hint: "Use tabctl group-list to see existing groups.",
740
- matches: allMatches.map((match) => summarizeGroupMatch(match, windowLabels)),
741
- availableGroups
742
- }
743
- };
744
- }
745
- if (matches.length > 1) {
746
- return {
747
- error: {
748
- message: `Ambiguous group title: found ${matches.length} groups named "${groupTitle}". Use group-gather to merge duplicates, --group-id to target by ID, or --window to narrow scope.`,
749
- hint: "Use group-gather to merge duplicates, --group-id to target by ID, or --window to narrow scope.",
750
- matches: matches.map((match) => summarizeGroupMatch(match, windowLabels)),
751
- availableGroups
752
- }
753
- };
754
- }
755
- return { match: matches[0] };
756
- }
757
- function resolveGroupById2(snapshot, buildWindowLabels2, groupId) {
758
- const windows = snapshot.windows;
759
- const matches = [];
760
- for (const win of windows) {
761
- const group = win.groups.find((entry) => entry.groupId === groupId);
762
- if (group) {
763
- matches.push({
764
- windowId: win.windowId,
765
- group,
766
- tabs: getGroupTabs(win, groupId)
767
- });
768
- }
769
- }
770
- if (matches.length === 0) {
771
- return {
772
- error: {
773
- message: "Group not found",
774
- hint: "Use tabctl group-list to see existing groups.",
775
- availableGroups: listGroupSummaries(snapshot, buildWindowLabels2)
776
- }
777
- };
778
- }
779
- if (matches.length > 1) {
780
- const windowLabels = buildWindowLabels2(snapshot);
781
- return {
782
- error: {
783
- message: "Group id is ambiguous. Provide a windowId.",
784
- hint: "Use --window to disambiguate group ids.",
785
- matches: matches.map((match) => summarizeGroupMatch(match, windowLabels)),
786
- availableGroups: listGroupSummaries(snapshot, buildWindowLabels2)
787
- }
788
- };
789
- }
790
- return { match: matches[0] };
791
- }
792
- async function listGroups2(params, deps2) {
793
- const snapshot = await deps2.getTabSnapshot();
794
- const windows = snapshot.windows;
795
- const windowLabels = deps2.buildWindowLabels(snapshot);
796
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) : null;
797
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
798
- throw new Error("Window not found");
799
- }
800
- const groups2 = [];
801
- for (const win of windows) {
802
- if (windowIdParam && win.windowId !== windowIdParam) {
803
- continue;
804
- }
805
- const counts = /* @__PURE__ */ new Map();
806
- for (const tab of win.tabs) {
807
- const groupId = tab.groupId;
808
- if (typeof groupId === "number" && groupId !== -1) {
809
- counts.set(groupId, (counts.get(groupId) || 0) + 1);
810
- }
811
- }
812
- for (const group of win.groups) {
813
- const groupId = group.groupId;
814
- groups2.push({
815
- windowId: win.windowId,
816
- windowLabel: windowLabels.get(win.windowId) ?? null,
817
- groupId,
818
- title: group.title ?? null,
819
- color: group.color ?? null,
820
- collapsed: group.collapsed ?? null,
821
- tabCount: counts.get(groupId) || 0
822
- });
823
- }
824
- }
825
- return { groups: groups2 };
826
- }
827
- async function groupUpdate2(params, deps2) {
828
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
829
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
830
- if (!groupId && !groupTitle) {
831
- throw new Error("Missing group identifier");
832
- }
833
- const snapshot = await deps2.getTabSnapshot();
834
- const windows = snapshot.windows;
835
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) : null;
836
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
837
- throw new Error("Window not found");
838
- }
839
- let match;
840
- if (groupId != null) {
841
- const resolved = resolveGroupById2(snapshot, deps2.buildWindowLabels, groupId);
842
- if (resolved.error) {
843
- throw resolved.error;
844
- }
845
- match = resolved.match;
846
- if (windowIdParam && windowIdParam !== match.windowId) {
847
- throw new Error("Group is not in the specified window");
848
- }
849
- } else {
850
- const resolved = resolveGroupByTitle2(snapshot, deps2.buildWindowLabels, groupTitle, windowIdParam || void 0);
851
- if (resolved.error) {
852
- throw resolved.error;
853
- }
854
- match = resolved.match;
855
- }
856
- const update = {};
857
- if (typeof params.title === "string") {
858
- update.title = params.title;
859
- }
860
- if (typeof params.color === "string" && params.color.trim()) {
861
- update.color = params.color.trim();
862
- }
863
- if (typeof params.collapsed === "boolean") {
864
- update.collapsed = params.collapsed;
865
- }
866
- if (!Object.keys(update).length) {
867
- throw new Error("Missing group update fields");
868
- }
869
- const updated = await chrome.tabGroups.update(match.group.groupId, update);
870
- const fullResult = {
871
- groupId: updated.id,
872
- windowId: updated.windowId,
873
- title: updated.title,
874
- color: updated.color,
875
- collapsed: updated.collapsed,
876
- undo: {
877
- action: "group-update",
878
- groupId: updated.id,
879
- windowId: match.windowId,
880
- previous: {
881
- title: match.group.title ?? null,
882
- color: match.group.color ?? null,
883
- collapsed: match.group.collapsed ?? null
884
- }
885
- },
886
- txid: params.txid || null
887
- };
888
- return {
889
- groupId: updated.id,
890
- windowId: updated.windowId,
891
- summary: { updatedGroups: 1 },
892
- undo: fullResult.undo,
893
- txid: fullResult.txid
894
- };
895
- }
896
- async function groupUngroup2(params, deps2) {
897
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
898
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
899
- if (!groupId && !groupTitle) {
900
- throw new Error("Missing group identifier");
901
- }
902
- const snapshot = await deps2.getTabSnapshot();
903
- const windows = snapshot.windows;
904
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) : null;
905
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
906
- throw new Error("Window not found");
907
- }
908
- let match;
909
- if (groupId != null) {
910
- const resolved = resolveGroupById2(snapshot, deps2.buildWindowLabels, groupId);
911
- if (resolved.error) {
912
- throw resolved.error;
913
- }
914
- match = resolved.match;
915
- if (windowIdParam && windowIdParam !== match.windowId) {
916
- throw new Error("Group is not in the specified window");
917
- }
918
- } else {
919
- const resolved = resolveGroupByTitle2(snapshot, deps2.buildWindowLabels, groupTitle, windowIdParam || void 0);
920
- if (resolved.error) {
921
- throw resolved.error;
922
- }
923
- match = resolved.match;
924
- }
925
- const undoTabs = match.tabs.map((tab) => ({
926
- tabId: tab.tabId,
927
- windowId: tab.windowId,
928
- index: tab.index,
929
- groupId: tab.groupId,
930
- groupTitle: tab.groupTitle,
931
- groupColor: tab.groupColor,
932
- groupCollapsed: match.group.collapsed ?? null
933
- })).filter((tab) => typeof tab.tabId === "number");
934
- const tabIds = match.tabs.map((tab) => tab.tabId).filter((tabId) => typeof tabId === "number");
935
- if (tabIds.length) {
936
- await chrome.tabs.ungroup(tabIds);
937
- }
938
- const fullResult = {
939
- groupId: match.group.groupId,
940
- groupTitle: match.group.title || null,
941
- windowId: match.windowId,
942
- summary: {
943
- ungroupedTabs: tabIds.length
944
- },
945
- undo: {
946
- action: "group-ungroup",
947
- groupId: match.group.groupId,
948
- windowId: match.windowId,
949
- groupTitle: match.group.title || null,
950
- groupColor: match.group.color || null,
951
- groupCollapsed: match.group.collapsed ?? null,
952
- tabs: undoTabs
953
- },
954
- txid: params.txid || null
955
- };
956
- return {
957
- groupId: match.group.groupId,
958
- windowId: match.windowId,
959
- summary: fullResult.summary,
960
- undo: fullResult.undo,
961
- txid: fullResult.txid
962
- };
963
- }
964
- async function groupAssign2(params, deps2) {
965
- const rawTabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
966
- const tabIds = rawTabIds.filter((id) => Number.isFinite(id));
967
- if (!tabIds.length) {
968
- throw new Error("Missing tabIds");
969
- }
970
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
971
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
972
- if (!groupId && !groupTitle) {
973
- throw new Error("Missing group identifier");
974
- }
975
- const snapshot = await deps2.getTabSnapshot();
976
- const windows = snapshot.windows;
977
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) : null;
978
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
979
- throw new Error("Window not found");
980
- }
981
- const tabIndex = /* @__PURE__ */ new Map();
982
- for (const win of windows) {
983
- for (const tab of win.tabs) {
984
- if (typeof tab.tabId === "number") {
985
- tabIndex.set(tab.tabId, { tab, windowId: win.windowId });
986
- }
987
- }
988
- }
989
- const skipped = [];
990
- const resolvedTabIds = [];
991
- const sourceWindows = /* @__PURE__ */ new Set();
992
- const undoTabs = [];
993
- for (const tabId of tabIds) {
994
- const entry = tabIndex.get(tabId);
995
- if (!entry) {
996
- skipped.push({ tabId, reason: "not_found" });
997
- continue;
998
- }
999
- resolvedTabIds.push(tabId);
1000
- sourceWindows.add(entry.windowId);
1001
- const tab = entry.tab;
1002
- undoTabs.push({
1003
- tabId,
1004
- windowId: entry.windowId,
1005
- index: tab.index,
1006
- groupId: tab.groupId,
1007
- groupTitle: tab.groupTitle,
1008
- groupColor: tab.groupColor,
1009
- groupCollapsed: tab.groupCollapsed ?? null
1010
- });
1011
- }
1012
- if (!resolvedTabIds.length) {
1013
- throw new Error("No matching tabs found");
1014
- }
1015
- let targetGroupId = null;
1016
- let targetWindowId = null;
1017
- let targetTitle = null;
1018
- let created = false;
1019
- if (groupId != null) {
1020
- const resolved = resolveGroupById2(snapshot, deps2.buildWindowLabels, groupId);
1021
- if (resolved.error) {
1022
- throw resolved.error;
1023
- }
1024
- const match = resolved.match;
1025
- targetGroupId = match.group.groupId;
1026
- targetWindowId = match.windowId;
1027
- targetTitle = typeof match.group.title === "string" ? match.group.title : null;
1028
- if (windowIdParam && windowIdParam !== targetWindowId) {
1029
- throw new Error("Group is not in the specified window");
1030
- }
1031
- } else {
1032
- const resolved = resolveGroupByTitle2(snapshot, deps2.buildWindowLabels, groupTitle, windowIdParam || void 0);
1033
- if (resolved.error) {
1034
- const error = resolved.error;
1035
- if (error.message === "No matching group title found" && params.create === true) {
1036
- targetWindowId = windowIdParam || (sourceWindows.size === 1 ? Array.from(sourceWindows)[0] : null);
1037
- if (!targetWindowId) {
1038
- throw new Error("Multiple source windows. Provide --window to create a new group.");
1039
- }
1040
- targetTitle = groupTitle;
1041
- created = true;
1042
- } else {
1043
- throw error;
1044
- }
1045
- } else {
1046
- const match = resolved.match;
1047
- targetGroupId = match.group.groupId;
1048
- targetWindowId = match.windowId;
1049
- targetTitle = typeof match.group.title === "string" && match.group.title ? match.group.title : groupTitle;
1050
- }
1051
- }
1052
- if (!targetWindowId) {
1053
- throw new Error("Target window not found");
1054
- }
1055
- const moveIds = resolvedTabIds.filter((tabId) => {
1056
- const entry = tabIndex.get(tabId);
1057
- return entry && entry.windowId !== targetWindowId;
1058
- });
1059
- if (moveIds.length > 0) {
1060
- await chrome.tabs.move(moveIds, { windowId: targetWindowId, index: -1 });
1061
- }
1062
- let assignedGroupId = targetGroupId;
1063
- if (targetGroupId != null) {
1064
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: resolvedTabIds });
1065
- } else {
1066
- assignedGroupId = await chrome.tabs.group({ tabIds: resolvedTabIds, createProperties: { windowId: targetWindowId } });
1067
- const update = {};
1068
- if (targetTitle) {
1069
- update.title = targetTitle;
1070
- }
1071
- if (typeof params.color === "string" && params.color.trim()) {
1072
- update.color = params.color.trim();
1073
- }
1074
- if (typeof params.collapsed === "boolean") {
1075
- update.collapsed = params.collapsed;
1076
- }
1077
- if (Object.keys(update).length > 0) {
1078
- try {
1079
- await chrome.tabGroups.update(assignedGroupId, update);
1080
- } catch (error) {
1081
- deps2.log("Failed to update group", error);
1082
- }
1083
- }
1084
- created = true;
1085
- }
1086
- const fullResult = {
1087
- groupId: assignedGroupId,
1088
- groupTitle: targetTitle || groupTitle || null,
1089
- windowId: targetWindowId,
1090
- created,
1091
- summary: {
1092
- movedTabs: moveIds.length,
1093
- groupedTabs: resolvedTabIds.length,
1094
- skippedTabs: skipped.length
1095
- },
1096
- skipped,
1097
- undo: {
1098
- action: "group-assign",
1099
- groupId: assignedGroupId,
1100
- groupTitle: targetTitle || groupTitle || null,
1101
- groupColor: typeof params.color === "string" && params.color.trim() ? params.color.trim() : null,
1102
- groupCollapsed: typeof params.collapsed === "boolean" ? params.collapsed : null,
1103
- created,
1104
- tabs: undoTabs
1105
- },
1106
- txid: params.txid || null
1107
- };
1108
- return {
1109
- groupId: assignedGroupId,
1110
- windowId: targetWindowId,
1111
- created,
1112
- summary: fullResult.summary,
1113
- skipped: fullResult.skipped,
1114
- undo: fullResult.undo,
1115
- txid: fullResult.txid
1116
- };
1117
- }
1118
- async function groupGather2(params, deps2) {
1119
- const snapshot = await deps2.getTabSnapshot();
1120
- const windows = snapshot.windows;
1121
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) : null;
1122
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
1123
- throw new Error("Window not found");
1124
- }
1125
- const groupTitleFilter = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
1126
- const merged = [];
1127
- const undoEntries = [];
1128
- for (const win of windows) {
1129
- if (windowIdParam && win.windowId !== windowIdParam)
1130
- continue;
1131
- const byTitle = /* @__PURE__ */ new Map();
1132
- for (const group of win.groups) {
1133
- const title = typeof group.title === "string" ? group.title : "";
1134
- if (!title)
1135
- continue;
1136
- if (groupTitleFilter && title !== groupTitleFilter)
1137
- continue;
1138
- if (!byTitle.has(title))
1139
- byTitle.set(title, []);
1140
- byTitle.get(title).push(group);
1141
- }
1142
- for (const [title, titleGroups] of byTitle) {
1143
- if (titleGroups.length < 2)
1144
- continue;
1145
- const groupsWithIndex = titleGroups.map((g) => {
1146
- const tabs2 = win.tabs.filter((t) => t.groupId === g.groupId);
1147
- const minIndex = Math.min(...tabs2.map((t) => {
1148
- const idx = Number(t.index);
1149
- return Number.isFinite(idx) ? idx : Infinity;
1150
- }));
1151
- return { group: g, tabs: tabs2, minIndex };
1152
- });
1153
- groupsWithIndex.sort((a, b) => a.minIndex - b.minIndex);
1154
- const primary = groupsWithIndex[0];
1155
- const duplicates = groupsWithIndex.slice(1);
1156
- let movedTabs = 0;
1157
- for (const dup of duplicates) {
1158
- const tabIds = dup.tabs.map((t) => t.tabId).filter((id) => typeof id === "number");
1159
- if (tabIds.length > 0) {
1160
- for (const tab of dup.tabs) {
1161
- undoEntries.push({
1162
- tabId: tab.tabId,
1163
- windowId: win.windowId,
1164
- index: tab.index,
1165
- groupId: tab.groupId,
1166
- groupTitle: tab.groupTitle,
1167
- groupColor: tab.groupColor,
1168
- groupCollapsed: dup.group.collapsed ?? null
1169
- });
1170
- }
1171
- await chrome.tabs.group({ groupId: primary.group.groupId, tabIds });
1172
- movedTabs += tabIds.length;
1173
- }
1174
- }
1175
- merged.push({
1176
- windowId: win.windowId,
1177
- groupTitle: title,
1178
- primaryGroupId: primary.group.groupId,
1179
- mergedGroupCount: duplicates.length,
1180
- movedTabs
1181
- });
1182
- }
1183
- }
1184
- const fullResult = {
1185
- merged,
1186
- summary: {
1187
- mergedGroups: merged.reduce((sum, m) => sum + m.mergedGroupCount, 0),
1188
- movedTabs: merged.reduce((sum, m) => sum + m.movedTabs, 0)
1189
- },
1190
- undo: {
1191
- action: "group-gather",
1192
- tabs: undoEntries
1193
- },
1194
- txid: params.txid || null
1195
- };
1196
- return {
1197
- merged,
1198
- summary: fullResult.summary,
1199
- undo: fullResult.undo,
1200
- txid: fullResult.txid
1201
- };
1202
- }
1203
449
  }
1204
450
  });
1205
451
 
1206
- // node_modules/normalize-url/index.js
1207
- var normalize_url_exports = {};
1208
- __export(normalize_url_exports, {
1209
- default: () => normalizeUrl
1210
- });
1211
- function normalizeUrl(urlString, options) {
1212
- options = {
1213
- defaultProtocol: "http",
1214
- normalizeProtocol: true,
1215
- forceHttp: false,
1216
- forceHttps: false,
1217
- stripAuthentication: true,
1218
- stripHash: false,
1219
- stripTextFragment: true,
1220
- stripWWW: true,
1221
- removeQueryParameters: [/^utm_\w+/i],
1222
- removeTrailingSlash: true,
1223
- removeSingleSlash: true,
1224
- removeDirectoryIndex: false,
1225
- removeExplicitPort: false,
1226
- sortQueryParameters: true,
1227
- removePath: false,
1228
- transformPath: false,
1229
- ...options
1230
- };
1231
- if (typeof options.defaultProtocol === "string" && !options.defaultProtocol.endsWith(":")) {
1232
- options.defaultProtocol = `${options.defaultProtocol}:`;
1233
- }
1234
- urlString = urlString.trim();
1235
- if (/^data:/i.test(urlString)) {
1236
- return normalizeDataURL(urlString, options);
1237
- }
1238
- if (hasCustomProtocol(urlString)) {
1239
- return urlString;
1240
- }
1241
- const hasRelativeProtocol = urlString.startsWith("//");
1242
- const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);
1243
- if (!isRelativeUrl) {
1244
- urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol);
1245
- }
1246
- const urlObject = new URL(urlString);
1247
- if (options.forceHttp && options.forceHttps) {
1248
- throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");
1249
- }
1250
- if (options.forceHttp && urlObject.protocol === "https:") {
1251
- urlObject.protocol = "http:";
452
+ // dist/extension/background.js
453
+ var HOST_NAME = "com.erwinkroon.tabctl";
454
+ var manifest = chrome.runtime.getManifest();
455
+ var MANIFEST_VERSION = manifest.version || "0.0.0";
456
+ var MANIFEST_VERSION_NAME = manifest.version_name || MANIFEST_VERSION;
457
+ function parseVersionName(versionName) {
458
+ const match = versionName.match(/-dev\.([0-9a-f]+)(\.dirty)?$/i);
459
+ if (!match) {
460
+ return { gitSha: null, dirty: false };
1252
461
  }
1253
- if (options.forceHttps && urlObject.protocol === "http:") {
1254
- urlObject.protocol = "https:";
462
+ return { gitSha: match[1] || null, dirty: Boolean(match[2]) };
463
+ }
464
+ var parsed = parseVersionName(MANIFEST_VERSION_NAME);
465
+ var VERSION_INFO = {
466
+ version: MANIFEST_VERSION_NAME,
467
+ baseVersion: MANIFEST_VERSION,
468
+ gitSha: parsed.gitSha,
469
+ dirty: parsed.dirty
470
+ };
471
+ var KEEPALIVE_ALARM = "tabctl-keepalive";
472
+ var KEEPALIVE_INTERVAL_MINUTES = 1;
473
+ var screenshot = require_screenshot();
474
+ var content = require_content();
475
+ var { delay, executeWithTimeout } = content;
476
+ var DESCRIPTION_MAX_LENGTH = 250;
477
+ function requireFiniteId(value, name) {
478
+ const n = Number(value);
479
+ if (!Number.isFinite(n))
480
+ throw new Error(`${name} must be a finite number, got: ${String(value)}`);
481
+ return n;
482
+ }
483
+ var state = {
484
+ port: null,
485
+ lastFocused: {},
486
+ lastFocusedLoaded: false
487
+ };
488
+ function log(...args) {
489
+ console.log("[tabctl]", ...args);
490
+ }
491
+ function sendResponse(id, ok, payload) {
492
+ if (!state.port) {
493
+ return;
1255
494
  }
1256
- if (options.stripAuthentication) {
1257
- urlObject.username = "";
1258
- urlObject.password = "";
495
+ if (ok) {
496
+ const data = typeof payload === "object" && payload !== null ? payload : { payload };
497
+ state.port.postMessage({ id, ok: true, data });
498
+ return;
1259
499
  }
1260
- if (options.stripHash) {
1261
- urlObject.hash = "";
1262
- } else if (options.stripTextFragment) {
1263
- urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, "");
500
+ const error = payload instanceof Error ? { message: payload.message, stack: payload.stack } : payload;
501
+ state.port.postMessage({ id, ok: false, error });
502
+ }
503
+ function connectNative() {
504
+ if (state.port) {
505
+ return;
1264
506
  }
1265
- if (urlObject.pathname) {
1266
- const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g;
1267
- let lastIndex = 0;
1268
- let result = "";
1269
- for (; ; ) {
1270
- const match = protocolRegex.exec(urlObject.pathname);
1271
- if (!match) {
1272
- break;
507
+ try {
508
+ const port = chrome.runtime.connectNative(HOST_NAME);
509
+ state.port = port;
510
+ port.onMessage.addListener(handleNativeMessage);
511
+ port.onDisconnect.addListener(() => {
512
+ const lastError = chrome.runtime.lastError;
513
+ if (lastError) {
514
+ log("Native host disconnected:", lastError.message);
515
+ } else {
516
+ log("Native host disconnected");
1273
517
  }
1274
- const protocol = match[0];
1275
- const protocolAtIndex = match.index;
1276
- const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);
1277
- result += intermediate.replace(/\/{2,}/g, "/");
1278
- result += protocol;
1279
- lastIndex = protocolAtIndex + protocol.length;
1280
- }
1281
- const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);
1282
- result += remnant.replace(/\/{2,}/g, "/");
1283
- urlObject.pathname = result;
1284
- }
1285
- if (urlObject.pathname) {
1286
- try {
1287
- urlObject.pathname = decodeURI(urlObject.pathname).replace(/\\/g, "%5C");
1288
- } catch {
1289
- }
1290
- }
1291
- if (options.removeDirectoryIndex === true) {
1292
- options.removeDirectoryIndex = [/^index\.[a-z]+$/];
1293
- }
1294
- if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
1295
- const pathComponents = urlObject.pathname.split("/").filter(Boolean);
1296
- const lastComponent = pathComponents.at(-1);
1297
- if (lastComponent && testParameter(lastComponent, options.removeDirectoryIndex)) {
1298
- pathComponents.pop();
1299
- urlObject.pathname = pathComponents.length > 0 ? `/${pathComponents.join("/")}/` : "/";
1300
- }
1301
- }
1302
- if (options.removePath) {
1303
- urlObject.pathname = "/";
518
+ state.port = null;
519
+ });
520
+ log("Native host connected");
521
+ } catch (error) {
522
+ log("Native host connection failed", error);
1304
523
  }
1305
- if (options.transformPath && typeof options.transformPath === "function") {
1306
- const pathComponents = urlObject.pathname.split("/").filter(Boolean);
1307
- const newComponents = options.transformPath(pathComponents);
1308
- urlObject.pathname = newComponents?.length > 0 ? `/${newComponents.join("/")}` : "/";
524
+ }
525
+ chrome.runtime.onInstalled.addListener(() => {
526
+ connectNative();
527
+ chrome.alarms.create(KEEPALIVE_ALARM, { periodInMinutes: KEEPALIVE_INTERVAL_MINUTES });
528
+ });
529
+ chrome.runtime.onStartup.addListener(() => {
530
+ connectNative();
531
+ chrome.alarms.create(KEEPALIVE_ALARM, { periodInMinutes: KEEPALIVE_INTERVAL_MINUTES });
532
+ });
533
+ chrome.alarms.onAlarm.addListener((alarm) => {
534
+ if (alarm.name === KEEPALIVE_ALARM) {
535
+ connectNative();
1309
536
  }
1310
- if (urlObject.hostname) {
1311
- urlObject.hostname = urlObject.hostname.replace(/\.$/, "");
1312
- if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) {
1313
- urlObject.hostname = urlObject.hostname.replace(/^www\./, "");
1314
- }
1315
- }
1316
- if (Array.isArray(options.removeQueryParameters)) {
1317
- for (const key of [...urlObject.searchParams.keys()]) {
1318
- if (testParameter(key, options.removeQueryParameters)) {
1319
- urlObject.searchParams.delete(key);
1320
- }
1321
- }
1322
- }
1323
- if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
1324
- urlObject.search = "";
1325
- }
1326
- if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
1327
- for (const key of [...urlObject.searchParams.keys()]) {
1328
- if (!testParameter(key, options.keepQueryParameters)) {
1329
- urlObject.searchParams.delete(key);
1330
- }
1331
- }
1332
- }
1333
- if (options.sortQueryParameters) {
1334
- const originalSearch = urlObject.search;
1335
- urlObject.searchParams.sort();
1336
- try {
1337
- urlObject.search = decodeURIComponent(urlObject.search);
1338
- } catch {
1339
- }
1340
- const partsWithoutEquals = originalSearch.slice(1).split("&").filter((p) => p && !p.includes("="));
1341
- for (const part of partsWithoutEquals) {
1342
- const decoded = decodeURIComponent(part);
1343
- urlObject.search = urlObject.search.replace(`?${decoded}=`, `?${decoded}`).replace(`&${decoded}=`, `&${decoded}`);
1344
- }
1345
- }
1346
- if (options.removeTrailingSlash) {
1347
- urlObject.pathname = urlObject.pathname.replace(/\/$/, "");
1348
- }
1349
- if (options.removeExplicitPort && urlObject.port) {
1350
- urlObject.port = "";
1351
- }
1352
- const oldUrlString = urlString;
1353
- urlString = urlObject.toString();
1354
- if (!options.removeSingleSlash && urlObject.pathname === "/" && !oldUrlString.endsWith("/") && urlObject.hash === "") {
1355
- urlString = urlString.replace(/\/$/, "");
1356
- }
1357
- if ((options.removeTrailingSlash || urlObject.pathname === "/") && urlObject.hash === "" && options.removeSingleSlash) {
1358
- urlString = urlString.replace(/\/$/, "");
1359
- }
1360
- if (hasRelativeProtocol && !options.normalizeProtocol) {
1361
- urlString = urlString.replace(/^http:\/\//, "//");
1362
- }
1363
- if (options.stripProtocol) {
1364
- urlString = urlString.replace(/^(?:https?:)?\/\//, "");
1365
- }
1366
- return urlString;
1367
- }
1368
- var DATA_URL_DEFAULT_MIME_TYPE, DATA_URL_DEFAULT_CHARSET, testParameter, supportedProtocols, hasCustomProtocol, normalizeDataURL;
1369
- var init_normalize_url = __esm({
1370
- "node_modules/normalize-url/index.js"() {
1371
- DATA_URL_DEFAULT_MIME_TYPE = "text/plain";
1372
- DATA_URL_DEFAULT_CHARSET = "us-ascii";
1373
- testParameter = (name, filters) => filters.some((filter) => filter instanceof RegExp ? filter.test(name) : filter === name);
1374
- supportedProtocols = /* @__PURE__ */ new Set([
1375
- "https:",
1376
- "http:",
1377
- "file:"
1378
- ]);
1379
- hasCustomProtocol = (urlString) => {
1380
- try {
1381
- const { protocol } = new URL(urlString);
1382
- return protocol.endsWith(":") && !protocol.includes(".") && !supportedProtocols.has(protocol);
1383
- } catch {
1384
- return false;
1385
- }
1386
- };
1387
- normalizeDataURL = (urlString, { stripHash }) => {
1388
- const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);
1389
- if (!match) {
1390
- throw new Error(`Invalid URL: ${urlString}`);
1391
- }
1392
- const { type, data, hash } = match.groups;
1393
- const mediaType = type.split(";");
1394
- const isBase64 = mediaType.at(-1) === "base64";
1395
- if (isBase64) {
1396
- mediaType.pop();
1397
- }
1398
- const mimeType = mediaType.shift()?.toLowerCase() ?? "";
1399
- const attributes = mediaType.map((attribute) => {
1400
- let [key, value = ""] = attribute.split("=").map((string) => string.trim());
1401
- if (key === "charset") {
1402
- value = value.toLowerCase();
1403
- if (value === DATA_URL_DEFAULT_CHARSET) {
1404
- return "";
1405
- }
1406
- }
1407
- return `${key}${value ? `=${value}` : ""}`;
1408
- }).filter(Boolean);
1409
- const normalizedMediaType = [...attributes];
1410
- if (isBase64) {
1411
- normalizedMediaType.push("base64");
1412
- }
1413
- if (normalizedMediaType.length > 0 || mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE) {
1414
- normalizedMediaType.unshift(mimeType);
1415
- }
1416
- const hashPart = stripHash || !hash ? "" : `#${hash}`;
1417
- return `data:${normalizedMediaType.join(";")},${isBase64 ? data.trim() : data}${hashPart}`;
1418
- };
1419
- }
1420
- });
1421
-
1422
- // dist/extension/lib/tabs.js
1423
- var require_tabs = __commonJS({
1424
- "dist/extension/lib/tabs.js"(exports) {
1425
- "use strict";
1426
- var __importDefault = exports && exports.__importDefault || function(mod) {
1427
- return mod && mod.__esModule ? mod : { "default": mod };
1428
- };
1429
- Object.defineProperty(exports, "__esModule", { value: true });
1430
- exports.getMostRecentFocusedWindowId = getMostRecentFocusedWindowId2;
1431
- exports.normalizeUrl = normalizeUrl2;
1432
- exports.normalizeTabIndex = normalizeTabIndex2;
1433
- exports.resolveOpenWindow = resolveOpenWindow;
1434
- exports.focusTab = focusTab;
1435
- exports.refreshTabs = refreshTabs;
1436
- exports.openTabs = openTabs;
1437
- var normalize_url_1 = __importDefault((init_normalize_url(), __toCommonJS(normalize_url_exports)));
1438
- function getMostRecentFocusedWindowId2(windows) {
1439
- let bestWindowId = null;
1440
- let bestFocusedAt = -Infinity;
1441
- for (const win of windows) {
1442
- for (const tab of win.tabs) {
1443
- const focusedAt = Number(tab.lastFocusedAt);
1444
- if (!Number.isFinite(focusedAt)) {
1445
- continue;
1446
- }
1447
- if (focusedAt > bestFocusedAt) {
1448
- bestFocusedAt = focusedAt;
1449
- bestWindowId = win.windowId;
1450
- }
1451
- }
1452
- }
1453
- return bestWindowId;
1454
- }
1455
- function normalizeUrl2(rawUrl) {
1456
- if (!rawUrl || typeof rawUrl !== "string") {
1457
- return null;
1458
- }
1459
- try {
1460
- return (0, normalize_url_1.default)(rawUrl, {
1461
- stripHash: true,
1462
- removeQueryParameters: [
1463
- /^utm_\w+$/i,
1464
- "fbclid",
1465
- "gclid",
1466
- "igshid",
1467
- "mc_cid",
1468
- "mc_eid",
1469
- "ref",
1470
- "ref_src",
1471
- "ref_url",
1472
- "si"
1473
- ]
1474
- });
1475
- } catch {
1476
- return null;
1477
- }
1478
- }
1479
- function normalizeTabIndex2(value) {
1480
- const index = Number(value);
1481
- return Number.isFinite(index) ? index : null;
1482
- }
1483
- function shapeOpenResult(result) {
1484
- return {
1485
- windowId: result.windowId,
1486
- groupId: result.groupId,
1487
- createdTabIds: result.created.map((tab) => tab.tabId).filter((id) => typeof id === "number"),
1488
- skipped: result.skipped,
1489
- summary: result.summary
1490
- };
1491
- }
1492
- function matchIncludes(value, needle) {
1493
- if (!needle) {
1494
- return false;
1495
- }
1496
- return typeof value === "string" && value.toLowerCase().includes(needle);
1497
- }
1498
- function resolveOpenWindow(snapshot, params) {
1499
- const windows = snapshot.windows;
1500
- if (!windows.length) {
1501
- return { error: { message: "No windows available" } };
1502
- }
1503
- if (params.windowId != null) {
1504
- if (typeof params.windowId === "string") {
1505
- const normalized = params.windowId.trim().toLowerCase();
1506
- if (normalized === "active") {
1507
- const focused2 = windows.find((win) => win.focused);
1508
- if (focused2) {
1509
- return { windowId: focused2.windowId };
1510
- }
1511
- return { error: { message: "Active window not found" } };
1512
- }
1513
- if (normalized === "last-focused") {
1514
- const lastFocused2 = getMostRecentFocusedWindowId2(windows);
1515
- if (lastFocused2 != null) {
1516
- return { windowId: lastFocused2 };
1517
- }
1518
- return { error: { message: "Last focused window not found" } };
1519
- }
1520
- if (normalized === "new") {
1521
- return { error: { message: "--window new is only supported by open" } };
1522
- }
1523
- }
1524
- const windowId = Number(params.windowId);
1525
- const found = windows.find((win) => win.windowId === windowId);
1526
- if (!found) {
1527
- return { error: { message: "Window not found" } };
1528
- }
1529
- return { windowId };
1530
- }
1531
- if (params.windowTabId != null) {
1532
- const tabId = Number(params.windowTabId);
1533
- const found = windows.find((win) => win.tabs.some((tab) => tab.tabId === tabId));
1534
- if (!found) {
1535
- return { error: { message: "Window not found for tab" } };
1536
- }
1537
- return { windowId: found.windowId };
1538
- }
1539
- let candidates = [...windows];
1540
- let filtered = false;
1541
- if (typeof params.afterGroupTitle === "string" && params.afterGroupTitle.trim()) {
1542
- const groupTitle = params.afterGroupTitle.trim();
1543
- candidates = candidates.filter((win) => win.groups.some((group) => group.title === groupTitle));
1544
- filtered = true;
1545
- }
1546
- if (typeof params.windowGroupTitle === "string" && params.windowGroupTitle.trim()) {
1547
- const groupTitle = params.windowGroupTitle.trim();
1548
- candidates = candidates.filter((win) => win.groups.some((group) => group.title === groupTitle));
1549
- filtered = true;
1550
- }
1551
- if (typeof params.windowUrl === "string" && params.windowUrl.trim()) {
1552
- const needle = params.windowUrl.trim().toLowerCase();
1553
- candidates = candidates.filter((win) => win.tabs.some((tab) => matchIncludes(tab.url, needle)));
1554
- filtered = true;
1555
- }
1556
- if (filtered) {
1557
- if (candidates.length === 1) {
1558
- return { windowId: candidates[0].windowId };
1559
- }
1560
- if (candidates.length === 0) {
1561
- return { error: { message: "No matching window found" } };
1562
- }
1563
- return { error: { message: "Multiple windows match selection. Provide --window to disambiguate." } };
1564
- }
1565
- const focused = windows.find((win) => win.focused);
1566
- if (focused) {
1567
- return { windowId: focused.windowId };
1568
- }
1569
- if (windows.length === 1) {
1570
- return { windowId: windows[0].windowId };
1571
- }
1572
- const lastFocused = getMostRecentFocusedWindowId2(windows);
1573
- if (lastFocused != null) {
1574
- return { windowId: lastFocused };
1575
- }
1576
- return { error: { message: "Multiple windows available. Provide --window to target one." } };
1577
- }
1578
- async function focusTab(params) {
1579
- const tabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
1580
- const tabId = Number.isFinite(params.tabId) ? Number(params.tabId) : tabIds.length ? Number(tabIds[0]) : null;
1581
- if (!tabId) {
1582
- throw new Error("Missing tabId");
1583
- }
1584
- const tab = await chrome.tabs.get(tabId);
1585
- await chrome.windows.update(tab.windowId, { focused: true });
1586
- await chrome.tabs.update(tabId, { active: true });
1587
- return {
1588
- tabId,
1589
- windowId: tab.windowId
1590
- };
1591
- }
1592
- async function refreshTabs(params) {
1593
- const tabId = Number.isFinite(params.tabId) ? Number(params.tabId) : null;
1594
- if (!tabId) {
1595
- throw new Error("Missing tabId");
1596
- }
1597
- await chrome.tabs.reload(tabId);
1598
- return {
1599
- tabId,
1600
- summary: { refreshedTabs: 1 }
1601
- };
1602
- }
1603
- async function openTabs(params, deps2) {
1604
- const urls = Array.isArray(params.urls) ? params.urls.map((url) => typeof url === "string" ? url.trim() : "").filter(Boolean) : [];
1605
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
1606
- const groupColor = typeof params.color === "string" ? params.color.trim() : "";
1607
- const afterGroupTitle = typeof params.afterGroupTitle === "string" ? params.afterGroupTitle.trim() : "";
1608
- const beforeTabId = Number.isFinite(params.beforeTabId) ? Number(params.beforeTabId) : null;
1609
- const afterTabId = Number.isFinite(params.afterTabId) ? Number(params.afterTabId) : null;
1610
- if (beforeTabId != null && afterTabId != null) {
1611
- throw new Error("Only one target position is allowed");
1612
- }
1613
- const newWindow = params.newWindow === true;
1614
- const forceNewGroup = params.newGroup === true;
1615
- const allowDuplicates = params.allowDuplicates === true;
1616
- if (!urls.length && !newWindow) {
1617
- throw new Error("No URLs provided");
1618
- }
1619
- if (newWindow) {
1620
- if (afterGroupTitle || beforeTabId || afterTabId) {
1621
- throw new Error("Cannot use --before/--after with --new-window");
1622
- }
1623
- if (params.windowId != null || params.windowGroupTitle || params.windowTabId != null || params.windowUrl) {
1624
- throw new Error("Cannot combine --new-window with window selectors");
1625
- }
1626
- const created2 = [];
1627
- const skipped2 = [];
1628
- const createdWindow = await chrome.windows.create({ focused: false });
1629
- const windowId2 = createdWindow.id;
1630
- let seedTabs = createdWindow.tabs;
1631
- if (!seedTabs) {
1632
- seedTabs = await chrome.tabs.query({ windowId: windowId2 });
1633
- }
1634
- const seedTabId = seedTabs.find((tab) => typeof tab.id === "number")?.id ?? null;
1635
- for (const url of urls) {
1636
- try {
1637
- const tab = await chrome.tabs.create({ windowId: windowId2, url, active: false });
1638
- created2.push({
1639
- tabId: tab.id,
1640
- windowId: tab.windowId,
1641
- index: tab.index,
1642
- url: tab.url,
1643
- title: tab.title
1644
- });
1645
- } catch (error) {
1646
- skipped2.push({ url, reason: "create_failed" });
1647
- }
1648
- }
1649
- if (!urls.length && seedTabs.length) {
1650
- const tab = seedTabs[0];
1651
- created2.push({
1652
- tabId: tab.id,
1653
- windowId: tab.windowId,
1654
- index: tab.index,
1655
- url: tab.url,
1656
- title: tab.title
1657
- });
1658
- }
1659
- if (seedTabId && created2.length > 0 && urls.length > 0) {
1660
- try {
1661
- await chrome.tabs.remove(seedTabId);
1662
- } catch (error) {
1663
- deps2.log("Failed to remove seed tab", error);
1664
- }
1665
- }
1666
- let groupId2 = null;
1667
- if (groupTitle && created2.length > 0) {
1668
- try {
1669
- const tabIds = created2.map((tab) => tab.tabId).filter((id) => typeof id === "number");
1670
- if (tabIds.length > 0) {
1671
- groupId2 = await chrome.tabs.group({ tabIds, createProperties: { windowId: windowId2 } });
1672
- const update = { title: groupTitle };
1673
- if (groupColor) {
1674
- update.color = groupColor;
1675
- }
1676
- await chrome.tabGroups.update(groupId2, update);
1677
- }
1678
- } catch (error) {
1679
- deps2.log("Failed to create group", error);
1680
- groupId2 = null;
1681
- }
1682
- }
1683
- return shapeOpenResult({
1684
- windowId: windowId2,
1685
- groupId: groupId2,
1686
- created: created2,
1687
- skipped: skipped2,
1688
- summary: {
1689
- createdTabs: created2.length,
1690
- skippedUrls: skipped2.length,
1691
- grouped: Boolean(groupId2)
1692
- }
1693
- });
1694
- }
1695
- const snapshot = await deps2.getTabSnapshot();
1696
- let openParams = params;
1697
- if (groupTitle && !forceNewGroup && openParams.windowId == null && !openParams.windowGroupTitle && !openParams.windowTabId && !openParams.windowUrl) {
1698
- const groupWindows = snapshot.windows.filter((win) => win.groups.some((g) => g.title === groupTitle));
1699
- if (groupWindows.length === 1) {
1700
- openParams = { ...openParams, windowId: groupWindows[0].windowId };
1701
- }
1702
- }
1703
- if (params.windowId == null && (beforeTabId != null || afterTabId != null)) {
1704
- const anchorId = beforeTabId != null ? beforeTabId : afterTabId;
1705
- const anchorWindow = snapshot.windows.find((win) => win.tabs.some((tab) => tab.tabId === anchorId));
1706
- if (anchorWindow) {
1707
- openParams = { ...params, windowId: anchorWindow.windowId };
1708
- }
1709
- }
1710
- const selection = resolveOpenWindow(snapshot, openParams);
1711
- if (selection.error) {
1712
- throw selection.error;
1713
- }
1714
- const windowId = selection.windowId;
1715
- const windowSnapshot = snapshot.windows.find((win) => win.windowId === windowId);
1716
- if (!windowSnapshot) {
1717
- throw new Error("Window snapshot unavailable");
1718
- }
1719
- let existingGroupId = null;
1720
- const existingUrlSet = /* @__PURE__ */ new Set();
1721
- if (groupTitle && !forceNewGroup) {
1722
- const matchingGroups = windowSnapshot.groups.filter((g) => g.title === groupTitle);
1723
- if (matchingGroups.length > 1) {
1724
- throw new Error(`Ambiguous group title "${groupTitle}": found ${matchingGroups.length} groups with the same name. Use --new-group to force a new group, group-gather to merge, or --group-id to target by ID.`);
1725
- }
1726
- if (matchingGroups.length === 1) {
1727
- existingGroupId = matchingGroups[0].groupId;
1728
- const existingTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === existingGroupId);
1729
- for (const tab of existingTabs) {
1730
- const norm = normalizeUrl2(tab.url);
1731
- if (norm) {
1732
- existingUrlSet.add(norm);
1733
- }
1734
- }
1735
- }
1736
- }
1737
- const created = [];
1738
- const skipped = [];
1739
- let insertIndex = null;
1740
- if (afterGroupTitle) {
1741
- const targetGroup = windowSnapshot.groups.find((group) => group.title === afterGroupTitle);
1742
- if (!targetGroup) {
1743
- throw new Error("Group not found in target window");
1744
- }
1745
- const groupTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === targetGroup.groupId);
1746
- if (!groupTabs.length) {
1747
- throw new Error("Group has no tabs to anchor insertion");
1748
- }
1749
- const indices = groupTabs.map((tab) => normalizeTabIndex2(tab.index)).filter((value) => value != null);
1750
- if (!indices.length) {
1751
- throw new Error("Group tabs missing indices");
1752
- }
1753
- insertIndex = Math.max(...indices) + 1;
1754
- }
1755
- if (beforeTabId != null || afterTabId != null) {
1756
- if (afterGroupTitle) {
1757
- throw new Error("Only one target position is allowed");
1758
- }
1759
- const anchorId = beforeTabId != null ? beforeTabId : afterTabId;
1760
- const anchorTab = windowSnapshot.tabs.find((tab) => tab.tabId === anchorId);
1761
- if (!anchorTab) {
1762
- throw new Error("Anchor tab not found in target window");
1763
- }
1764
- const anchorIndex = normalizeTabIndex2(anchorTab.index);
1765
- if (!Number.isFinite(anchorIndex)) {
1766
- throw new Error("Anchor tab index unavailable");
1767
- }
1768
- insertIndex = beforeTabId != null ? anchorIndex : anchorIndex + 1;
1769
- }
1770
- if (existingGroupId != null && insertIndex == null && beforeTabId == null && afterTabId == null) {
1771
- const groupTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === existingGroupId);
1772
- const indices = groupTabs.map((tab) => normalizeTabIndex2(tab.index)).filter((value) => value != null);
1773
- if (indices.length) {
1774
- insertIndex = Math.max(...indices) + 1;
1775
- }
1776
- }
1777
- let nextIndex = insertIndex;
1778
- for (const url of urls) {
1779
- if (!allowDuplicates && existingGroupId != null) {
1780
- const norm = normalizeUrl2(url);
1781
- if (norm && existingUrlSet.has(norm)) {
1782
- skipped.push({ url, reason: "duplicate" });
1783
- continue;
1784
- }
1785
- }
1786
- try {
1787
- const createOptions = { windowId, url, active: false };
1788
- if (nextIndex != null) {
1789
- createOptions.index = nextIndex;
1790
- nextIndex += 1;
1791
- }
1792
- const tab = await chrome.tabs.create(createOptions);
1793
- created.push({
1794
- tabId: tab.id,
1795
- windowId: tab.windowId,
1796
- index: tab.index,
1797
- url: tab.url,
1798
- title: tab.title
1799
- });
1800
- } catch (error) {
1801
- skipped.push({ url, reason: "create_failed" });
1802
- }
1803
- }
1804
- const createdTabIds = new Set(created.map((tab) => tab.tabId).filter((id) => typeof id === "number"));
1805
- let groupId = null;
1806
- if (groupTitle && created.length > 0) {
1807
- try {
1808
- const tabIds = created.map((tab) => tab.tabId).filter((id) => typeof id === "number");
1809
- if (tabIds.length > 0) {
1810
- if (existingGroupId != null) {
1811
- groupId = await chrome.tabs.group({ groupId: existingGroupId, tabIds });
1812
- } else {
1813
- groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId } });
1814
- const update = { title: groupTitle };
1815
- if (groupColor) {
1816
- update.color = groupColor;
1817
- }
1818
- await chrome.tabGroups.update(groupId, update);
1819
- }
1820
- }
1821
- } catch (error) {
1822
- deps2.log("Failed to create group", error);
1823
- groupId = null;
1824
- }
1825
- }
1826
- if (groupTitle && created.length === 0 && existingGroupId != null) {
1827
- groupId = existingGroupId;
1828
- }
1829
- const targetGroupId = groupId ?? existingGroupId;
1830
- if (targetGroupId != null && created.length > 0) {
1831
- try {
1832
- if (createdTabIds.size > 0) {
1833
- const latestTabs = await chrome.tabs.query({ windowId });
1834
- const missingGroupTabIds = latestTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
1835
- if (missingGroupTabIds.length > 0) {
1836
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
1837
- }
1838
- }
1839
- } catch (error) {
1840
- deps2.log("Failed to enforce grouping for newly opened tabs", error);
1841
- }
1842
- }
1843
- try {
1844
- const freshTabs = await chrome.tabs.query({ windowId });
1845
- freshTabs.sort((a, b) => a.index - b.index);
1846
- const firstUngroupedIndex = freshTabs.findIndex((t) => (t.groupId ?? -1) === -1);
1847
- if (firstUngroupedIndex >= 0) {
1848
- let tabIdsToMove = [];
1849
- if (existingGroupId != null) {
1850
- tabIdsToMove = freshTabs.filter((tab, i) => i > firstUngroupedIndex && typeof tab.id === "number" && createdTabIds.has(tab.id) && (tab.groupId ?? -1) !== -1).map((tab) => tab.id);
1851
- } else {
1852
- tabIdsToMove = freshTabs.filter((tab, i) => i > firstUngroupedIndex && (tab.groupId ?? -1) !== -1).map((tab) => tab.id).filter((id) => typeof id === "number");
1853
- }
1854
- if (tabIdsToMove.length > 0) {
1855
- await chrome.tabs.move(tabIdsToMove, { index: firstUngroupedIndex });
1856
- }
1857
- }
1858
- } catch (err) {
1859
- deps2.log("Failed to reorder groups before ungrouped tabs", err);
1860
- }
1861
- if (targetGroupId != null && createdTabIds.size > 0) {
1862
- try {
1863
- const latestTabs = await chrome.tabs.query({ windowId });
1864
- const lateUngroupedTabIds = latestTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
1865
- if (lateUngroupedTabIds.length > 0) {
1866
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: lateUngroupedTabIds });
1867
- }
1868
- } catch (error) {
1869
- deps2.log("Failed post-reorder grouping verification", error);
1870
- }
1871
- }
1872
- if (targetGroupId != null && createdTabIds.size > 0) {
1873
- try {
1874
- await deps2.delay(250);
1875
- const delayedTabs = await chrome.tabs.query({ windowId });
1876
- const delayedUngroupedTabIds = delayedTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
1877
- if (delayedUngroupedTabIds.length > 0) {
1878
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: delayedUngroupedTabIds });
1879
- }
1880
- } catch (error) {
1881
- deps2.log("Failed delayed grouping verification", error);
1882
- }
1883
- }
1884
- return shapeOpenResult({
1885
- windowId,
1886
- groupId,
1887
- created,
1888
- skipped,
1889
- summary: {
1890
- createdTabs: created.length,
1891
- skippedUrls: skipped.length,
1892
- grouped: Boolean(groupId)
1893
- }
1894
- });
1895
- }
1896
- }
1897
- });
1898
-
1899
- // dist/extension/lib/move.js
1900
- var require_move = __commonJS({
1901
- "dist/extension/lib/move.js"(exports) {
1902
- "use strict";
1903
- Object.defineProperty(exports, "__esModule", { value: true });
1904
- exports.resolveMoveTarget = resolveMoveTarget;
1905
- exports.moveTab = moveTab;
1906
- exports.moveGroup = moveGroup;
1907
- var tabs2 = require_tabs();
1908
- var { normalizeTabIndex: normalizeTabIndex2 } = tabs2;
1909
- function resolveMoveTarget(snapshot, params, deps2) {
1910
- const beforeTabId = Number(params.beforeTabId);
1911
- const afterTabId = Number(params.afterTabId);
1912
- const beforeGroupTitle = typeof params.beforeGroupTitle === "string" ? params.beforeGroupTitle.trim() : "";
1913
- const afterGroupTitle = typeof params.afterGroupTitle === "string" ? params.afterGroupTitle.trim() : "";
1914
- const targets = [
1915
- Number.isFinite(beforeTabId) ? "before-tab" : null,
1916
- Number.isFinite(afterTabId) ? "after-tab" : null,
1917
- beforeGroupTitle ? "before-group" : null,
1918
- afterGroupTitle ? "after-group" : null
1919
- ].filter(Boolean);
1920
- if (targets.length === 0) {
1921
- return { error: { message: "Missing target position (--before/--after)" } };
1922
- }
1923
- if (targets.length > 1) {
1924
- return { error: { message: "Only one target position is allowed" } };
1925
- }
1926
- const windows = snapshot.windows;
1927
- const findTab = (tabId) => {
1928
- for (const win of windows) {
1929
- const tab = win.tabs.find((entry) => entry.tabId === tabId);
1930
- if (tab) {
1931
- return { tab, windowId: win.windowId };
1932
- }
1933
- }
1934
- return null;
1935
- };
1936
- if (targets[0] === "before-tab" || targets[0] === "after-tab") {
1937
- const tabId = targets[0] === "before-tab" ? beforeTabId : afterTabId;
1938
- if (!Number.isFinite(tabId)) {
1939
- return { error: { message: "Invalid tab target" } };
1940
- }
1941
- const match2 = findTab(tabId);
1942
- if (!match2) {
1943
- return { error: { message: "Target tab not found" } };
1944
- }
1945
- const index = normalizeTabIndex2(match2.tab.index);
1946
- if (!Number.isFinite(index)) {
1947
- return { error: { message: "Target tab index unavailable" } };
1948
- }
1949
- return {
1950
- windowId: match2.windowId,
1951
- index: targets[0] === "before-tab" ? index : index + 1,
1952
- anchor: { type: "tab", tabId }
1953
- };
1954
- }
1955
- const groupTitle = targets[0] === "before-group" ? beforeGroupTitle : afterGroupTitle;
1956
- const windowId = Number.isFinite(params.windowId) ? Number(params.windowId) : void 0;
1957
- const resolved = deps2.resolveGroupByTitle(snapshot, groupTitle, windowId);
1958
- if (resolved.error) {
1959
- return resolved;
1960
- }
1961
- const match = resolved.match;
1962
- if (!match.tabs.length) {
1963
- return { error: { message: "Target group has no tabs" } };
1964
- }
1965
- const indices = match.tabs.map((tab) => normalizeTabIndex2(tab.index)).filter((value) => value != null);
1966
- if (!indices.length) {
1967
- return { error: { message: "Target group indices unavailable" } };
1968
- }
1969
- const minIndex = Math.min(...indices);
1970
- const maxIndex = Math.max(...indices);
1971
- return {
1972
- windowId: match.windowId,
1973
- index: targets[0] === "before-group" ? minIndex : maxIndex + 1,
1974
- anchor: { type: "group", groupId: match.group.groupId, groupTitle }
1975
- };
1976
- }
1977
- async function moveTab(params, deps2) {
1978
- const tabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
1979
- const tabId = Number.isFinite(params.tabId) ? Number(params.tabId) : tabIds.length ? Number(tabIds[0]) : null;
1980
- if (!tabId) {
1981
- throw new Error("Missing tabId");
1982
- }
1983
- const snapshot = await deps2.getTabSnapshot();
1984
- const windows = snapshot.windows;
1985
- const sourceWindow = windows.find((win) => win.tabs.some((tab) => tab.tabId === tabId));
1986
- if (!sourceWindow) {
1987
- throw new Error("Source tab not found");
1988
- }
1989
- const sourceTab = sourceWindow.tabs.find((tab) => tab.tabId === tabId);
1990
- if (!sourceTab) {
1991
- throw new Error("Source tab not found");
1992
- }
1993
- const newWindow = params.newWindow === true;
1994
- const hasTarget = Number.isFinite(params.beforeTabId) || Number.isFinite(params.afterTabId) || typeof params.beforeGroupTitle === "string" && params.beforeGroupTitle.trim() || typeof params.afterGroupTitle === "string" && params.afterGroupTitle.trim();
1995
- if (newWindow) {
1996
- if (hasTarget) {
1997
- throw new Error("Cannot combine --new-window with --before/--after");
1998
- }
1999
- const createdWindow = await chrome.windows.create({ tabId, focused: false });
2000
- const targetWindowId2 = createdWindow.id;
2001
- let targetIndex2 = 0;
2002
- const createdTab = createdWindow.tabs?.find((tab) => tab.id === tabId) || null;
2003
- if (createdTab && Number.isFinite(createdTab.index)) {
2004
- targetIndex2 = createdTab.index;
2005
- } else {
2006
- try {
2007
- const updated = await chrome.tabs.get(tabId);
2008
- targetIndex2 = updated.index;
2009
- } catch {
2010
- targetIndex2 = 0;
2011
- }
2012
- }
2013
- const fullResult2 = {
2014
- tabId,
2015
- from: { windowId: sourceWindow.windowId, index: sourceTab.index },
2016
- to: { windowId: targetWindowId2, index: targetIndex2 },
2017
- summary: { movedTabs: 1 },
2018
- undo: {
2019
- action: "move-tab",
2020
- tabId,
2021
- from: {
2022
- windowId: sourceWindow.windowId,
2023
- index: sourceTab.index,
2024
- groupId: sourceTab.groupId,
2025
- groupTitle: sourceTab.groupTitle,
2026
- groupColor: sourceTab.groupColor,
2027
- groupCollapsed: sourceTab.groupCollapsed ?? null
2028
- },
2029
- to: {
2030
- windowId: targetWindowId2,
2031
- index: targetIndex2
2032
- }
2033
- },
2034
- txid: params.txid || null
2035
- };
2036
- return {
2037
- tabId,
2038
- fromWindowId: sourceWindow.windowId,
2039
- toWindowId: targetWindowId2,
2040
- summary: fullResult2.summary,
2041
- undo: fullResult2.undo,
2042
- txid: fullResult2.txid
2043
- };
2044
- }
2045
- let normalizedParams = params;
2046
- if (params.windowId != null) {
2047
- const resolvedWindowId = deps2.resolveWindowIdFromParams(snapshot, params.windowId);
2048
- normalizedParams = { ...params, windowId: resolvedWindowId ?? void 0 };
2049
- }
2050
- const target = resolveMoveTarget(snapshot, normalizedParams, deps2);
2051
- if (target.error) {
2052
- throw target.error;
2053
- }
2054
- const targetWindowId = target.windowId;
2055
- let targetIndex = target.index;
2056
- const sourceIndex = normalizeTabIndex2(sourceTab.index);
2057
- if (Number.isFinite(sourceIndex) && sourceWindow.windowId === targetWindowId && sourceIndex < targetIndex) {
2058
- targetIndex -= 1;
2059
- }
2060
- const moved = await chrome.tabs.move(tabId, { windowId: targetWindowId, index: targetIndex });
2061
- const fullResult = {
2062
- tabId,
2063
- from: { windowId: sourceWindow.windowId, index: sourceTab.index },
2064
- to: { windowId: targetWindowId, index: moved.index },
2065
- summary: { movedTabs: 1 },
2066
- undo: {
2067
- action: "move-tab",
2068
- tabId,
2069
- from: {
2070
- windowId: sourceWindow.windowId,
2071
- index: sourceTab.index,
2072
- groupId: sourceTab.groupId,
2073
- groupTitle: sourceTab.groupTitle,
2074
- groupColor: sourceTab.groupColor,
2075
- groupCollapsed: sourceTab.groupCollapsed ?? null
2076
- },
2077
- to: {
2078
- windowId: targetWindowId,
2079
- index: moved.index
2080
- }
2081
- },
2082
- txid: params.txid || null
2083
- };
2084
- return {
2085
- tabId,
2086
- fromWindowId: sourceWindow.windowId,
2087
- toWindowId: targetWindowId,
2088
- summary: fullResult.summary,
2089
- undo: fullResult.undo,
2090
- txid: fullResult.txid
2091
- };
2092
- }
2093
- async function moveGroup(params, deps2) {
2094
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
2095
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
2096
- if (!groupId && !groupTitle) {
2097
- throw new Error("Missing group identifier");
2098
- }
2099
- const snapshot = await deps2.getTabSnapshot();
2100
- const windowIdParam = params.windowId != null ? deps2.resolveWindowIdFromParams(snapshot, params.windowId) ?? void 0 : void 0;
2101
- const resolvedGroup = groupId != null ? deps2.resolveGroupById(snapshot, groupId) : deps2.resolveGroupByTitle(snapshot, groupTitle, windowIdParam);
2102
- if (resolvedGroup.error) {
2103
- throw resolvedGroup.error;
2104
- }
2105
- const source = resolvedGroup.match;
2106
- if (!source.tabs.length) {
2107
- throw new Error("Group has no tabs to move");
2108
- }
2109
- const ensureMovedTabsAreGrouped = async (movedTabIds, targetWindowId2, targetGroupId) => {
2110
- if (!targetGroupId || movedTabIds.length === 0) {
2111
- return;
2112
- }
2113
- const movedSet = new Set(movedTabIds);
2114
- const verify = async (step) => {
2115
- const tabs3 = await chrome.tabs.query({ windowId: targetWindowId2 });
2116
- const missingGroupTabIds = tabs3.filter((tab) => typeof tab.id === "number" && movedSet.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
2117
- if (missingGroupTabIds.length > 0) {
2118
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
2119
- }
2120
- };
2121
- try {
2122
- await verify("group-verify");
2123
- await new Promise((resolve) => setTimeout(resolve, 250));
2124
- await verify("group-verify-delayed");
2125
- await new Promise((resolve) => setTimeout(resolve, 1500));
2126
- await verify("group-verify-delayed-late");
2127
- } catch (error) {
2128
- deps2.log("Failed to enforce moved group integrity", error);
2129
- }
2130
- };
2131
- const newWindow = params.newWindow === true;
2132
- const hasTarget = Number.isFinite(params.beforeTabId) || Number.isFinite(params.afterTabId) || typeof params.beforeGroupTitle === "string" && params.beforeGroupTitle.trim() || typeof params.afterGroupTitle === "string" && params.afterGroupTitle.trim();
2133
- if (newWindow) {
2134
- if (hasTarget) {
2135
- throw new Error("Cannot combine --new-window with --before/--after");
2136
- }
2137
- const tabIds2 = source.tabs.map((tab) => tab.tabId).filter((id) => typeof id === "number");
2138
- const [firstTabId, ...restTabIds] = tabIds2;
2139
- if (!firstTabId) {
2140
- throw new Error("Group has no tabs to move");
2141
- }
2142
- const createdWindow = await chrome.windows.create({ tabId: firstTabId, focused: false });
2143
- const targetWindowId2 = createdWindow.id;
2144
- if (restTabIds.length > 0) {
2145
- await chrome.tabs.move(restTabIds, { windowId: targetWindowId2, index: -1 });
2146
- }
2147
- let newGroupId2 = null;
2148
- try {
2149
- newGroupId2 = await chrome.tabs.group({ tabIds: tabIds2, createProperties: { windowId: targetWindowId2 } });
2150
- await chrome.tabGroups.update(newGroupId2, {
2151
- title: source.group.title || "",
2152
- color: source.group.color || "grey",
2153
- collapsed: source.group.collapsed || false
2154
- });
2155
- } catch (error) {
2156
- deps2.log("Failed to regroup tabs", error);
2157
- }
2158
- await ensureMovedTabsAreGrouped(tabIds2, targetWindowId2, newGroupId2);
2159
- const undoTabs2 = source.tabs.map((tab) => ({
2160
- tabId: tab.tabId,
2161
- windowId: tab.windowId,
2162
- index: tab.index,
2163
- groupId: tab.groupId,
2164
- groupTitle: tab.groupTitle,
2165
- groupColor: tab.groupColor,
2166
- groupCollapsed: source.group.collapsed ?? null
2167
- })).filter((tab) => typeof tab.tabId === "number");
2168
- const fullResult2 = {
2169
- groupId: source.group.groupId,
2170
- windowId: source.windowId,
2171
- movedToWindowId: targetWindowId2,
2172
- newGroupId: newGroupId2,
2173
- summary: { movedTabs: tabIds2.length },
2174
- undo: {
2175
- action: "move-group",
2176
- groupId: source.group.groupId,
2177
- windowId: source.windowId,
2178
- movedToWindowId: targetWindowId2,
2179
- groupTitle: source.group.title ?? null,
2180
- groupColor: source.group.color ?? null,
2181
- groupCollapsed: source.group.collapsed ?? null,
2182
- tabs: undoTabs2
2183
- },
2184
- txid: params.txid || null
2185
- };
2186
- return {
2187
- groupId: source.group.groupId,
2188
- windowId: source.windowId,
2189
- movedToWindowId: targetWindowId2,
2190
- newGroupId: newGroupId2,
2191
- summary: fullResult2.summary,
2192
- undo: fullResult2.undo,
2193
- txid: fullResult2.txid
2194
- };
2195
- }
2196
- const target = resolveMoveTarget(snapshot, params, deps2);
2197
- if (target.error) {
2198
- throw target.error;
2199
- }
2200
- if (target.anchor?.type === "tab") {
2201
- const anchorTabId = target.anchor.tabId;
2202
- if (source.tabs.some((tab) => tab.tabId === anchorTabId)) {
2203
- throw new Error("Target tab is within the source group");
2204
- }
2205
- }
2206
- if (target.anchor?.type === "group") {
2207
- const anchorGroupId = target.anchor.groupId;
2208
- if (anchorGroupId === source.group.groupId && source.windowId === target.windowId) {
2209
- throw new Error("Target group matches source group");
2210
- }
2211
- }
2212
- const tabIds = source.tabs.map((tab) => tab.tabId).filter((id) => typeof id === "number");
2213
- const indices = source.tabs.map((tab) => normalizeTabIndex2(tab.index)).filter((value) => value != null);
2214
- const minIndex = Math.min(...indices);
2215
- const maxIndex = Math.max(...indices);
2216
- const targetWindowId = target.windowId;
2217
- let targetIndex = target.index;
2218
- if (source.windowId === targetWindowId && targetIndex > maxIndex) {
2219
- targetIndex -= tabIds.length;
2220
- }
2221
- const moved = await chrome.tabs.move(tabIds, { windowId: targetWindowId, index: targetIndex });
2222
- const movedList = Array.isArray(moved) ? moved : [moved];
2223
- let newGroupId = null;
2224
- if (targetWindowId !== source.windowId) {
2225
- try {
2226
- const movedIds = movedList.map((tab) => tab.id).filter((id) => typeof id === "number");
2227
- if (movedIds.length > 0) {
2228
- newGroupId = await chrome.tabs.group({ tabIds: movedIds, createProperties: { windowId: targetWindowId } });
2229
- await chrome.tabGroups.update(newGroupId, {
2230
- title: source.group.title || "",
2231
- color: source.group.color || "grey",
2232
- collapsed: source.group.collapsed || false
2233
- });
2234
- }
2235
- } catch (error) {
2236
- deps2.log("Failed to regroup tabs", error);
2237
- }
2238
- }
2239
- await ensureMovedTabsAreGrouped(tabIds, targetWindowId, targetWindowId === source.windowId ? source.group.groupId : newGroupId);
2240
- const undoTabs = source.tabs.map((tab) => ({
2241
- tabId: tab.tabId,
2242
- windowId: tab.windowId,
2243
- index: tab.index,
2244
- groupId: tab.groupId,
2245
- groupTitle: tab.groupTitle,
2246
- groupColor: tab.groupColor,
2247
- groupCollapsed: source.group.collapsed ?? null
2248
- })).filter((tab) => typeof tab.tabId === "number");
2249
- const fullResult = {
2250
- groupId: source.group.groupId,
2251
- windowId: source.windowId,
2252
- movedToWindowId: targetWindowId,
2253
- newGroupId,
2254
- summary: { movedTabs: tabIds.length },
2255
- undo: {
2256
- action: "move-group",
2257
- groupId: source.group.groupId,
2258
- windowId: source.windowId,
2259
- movedToWindowId: targetWindowId,
2260
- groupTitle: source.group.title ?? null,
2261
- groupColor: source.group.color ?? null,
2262
- groupCollapsed: source.group.collapsed ?? null,
2263
- tabs: undoTabs
2264
- },
2265
- txid: params.txid || null
2266
- };
2267
- return {
2268
- groupId: source.group.groupId,
2269
- windowId: source.windowId,
2270
- movedToWindowId: targetWindowId,
2271
- newGroupId,
2272
- summary: fullResult.summary,
2273
- undo: fullResult.undo,
2274
- txid: fullResult.txid
2275
- };
2276
- }
2277
- }
2278
- });
2279
-
2280
- // dist/extension/lib/inspect.js
2281
- var require_inspect = __commonJS({
2282
- "dist/extension/lib/inspect.js"(exports) {
2283
- "use strict";
2284
- Object.defineProperty(exports, "__esModule", { value: true });
2285
- exports.SELECTOR_VALUE_MAX_LENGTH = exports.DESCRIPTION_MAX_LENGTH = exports.DEFAULT_STALE_DAYS = void 0;
2286
- exports.analyzeTabs = analyzeTabs;
2287
- exports.inspectTabs = inspectTabs;
2288
- var content2 = require_content();
2289
- var { isScriptableUrl: isScriptableUrl2, extractPageMeta: extractPageMeta2, extractSelectorSignal: extractSelectorSignal2, waitForSettle: waitForSettle2, waitForTabReady: waitForTabReady2 } = content2;
2290
- var tabs2 = require_tabs();
2291
- var { normalizeUrl: normalizeUrl2 } = tabs2;
2292
- exports.DEFAULT_STALE_DAYS = 30;
2293
- exports.DESCRIPTION_MAX_LENGTH = 250;
2294
- exports.SELECTOR_VALUE_MAX_LENGTH = 500;
2295
- async function analyzeTabs(params, requestId, deps2) {
2296
- const staleDays = Number.isFinite(params.staleDays) ? params.staleDays : exports.DEFAULT_STALE_DAYS;
2297
- const progressEnabled = params.progress === true;
2298
- const snapshot = await deps2.getTabSnapshot();
2299
- const selection = deps2.selectTabsByScope(snapshot, params);
2300
- if (selection.error) {
2301
- throw selection.error;
2302
- }
2303
- const selectedTabs = selection.tabs;
2304
- const scopeTabs = selectedTabs;
2305
- const now = Date.now();
2306
- const normalizedMap = /* @__PURE__ */ new Map();
2307
- const duplicates = /* @__PURE__ */ new Map();
2308
- for (const tab of scopeTabs) {
2309
- const normalized = normalizeUrl2(tab.url);
2310
- if (!normalized) {
2311
- continue;
2312
- }
2313
- if (normalizedMap.has(normalized)) {
2314
- const existing = normalizedMap.get(normalized);
2315
- duplicates.set(tab.tabId, existing.tabId);
2316
- } else {
2317
- normalizedMap.set(normalized, tab);
2318
- }
2319
- }
2320
- const candidateMap = /* @__PURE__ */ new Map();
2321
- const addReason = (tab, reason) => {
2322
- const tabId = tab.tabId;
2323
- const entry = candidateMap.get(tabId) || { tab, reasons: [] };
2324
- entry.reasons.push(reason);
2325
- candidateMap.set(tabId, entry);
2326
- };
2327
- for (const tab of selectedTabs) {
2328
- if (duplicates.has(tab.tabId)) {
2329
- addReason(tab, {
2330
- type: "duplicate",
2331
- detail: `Matches tab ${duplicates.get(tab.tabId)}`
2332
- });
2333
- }
2334
- if (tab.lastFocusedAt) {
2335
- const ageDays = (now - tab.lastFocusedAt) / (24 * 60 * 60 * 1e3);
2336
- if (ageDays >= staleDays) {
2337
- addReason(tab, {
2338
- type: "stale",
2339
- detail: `Last focused ${Math.floor(ageDays)} days ago`
2340
- });
2341
- }
2342
- }
2343
- }
2344
- const candidates = Array.from(candidateMap.values()).map((entry) => {
2345
- const reasons = entry.reasons;
2346
- const severity = reasons.some((reason) => reason.type === "duplicate") ? "high" : "medium";
2347
- return {
2348
- tabId: entry.tab.tabId,
2349
- windowId: entry.tab.windowId,
2350
- groupId: entry.tab.groupId,
2351
- url: entry.tab.url,
2352
- title: entry.tab.title,
2353
- lastFocusedAt: entry.tab.lastFocusedAt,
2354
- reasons,
2355
- severity
2356
- };
2357
- });
2358
- const response = {
2359
- staleDays,
2360
- totals: {
2361
- tabs: scopeTabs.length,
2362
- analyzed: selectedTabs.length,
2363
- candidates: candidates.length
2364
- },
2365
- candidates
2366
- };
2367
- return response;
2368
- }
2369
- async function inspectTabs(params, requestId, deps2) {
2370
- const signalList = Array.isArray(params.signals) && params.signals.length > 0 ? params.signals.map(String) : ["page-meta"];
2371
- const signalConcurrencyRaw = Number(params.signalConcurrency);
2372
- const signalConcurrency = Number.isFinite(signalConcurrencyRaw) && signalConcurrencyRaw > 0 ? Math.min(10, Math.floor(signalConcurrencyRaw)) : 4;
2373
- const signalTimeoutRaw = Number(params.signalTimeoutMs);
2374
- const signalTimeoutMs = Number.isFinite(signalTimeoutRaw) && signalTimeoutRaw > 0 ? Math.floor(signalTimeoutRaw) : 4e3;
2375
- const progressEnabled = params.progress === true;
2376
- const waitFor = typeof params.waitFor === "string" ? params.waitFor.trim().toLowerCase() : "";
2377
- if (waitFor === "settle" && Array.isArray(params.tabIds) && params.tabIds.length > 0) {
2378
- const waitTimeoutRaw = Number(params.waitTimeoutMs);
2379
- const waitTimeoutMs = Number.isFinite(waitTimeoutRaw) && waitTimeoutRaw > 0 ? Math.floor(waitTimeoutRaw) : signalTimeoutMs;
2380
- const tabIds = params.tabIds.map(Number).filter(Number.isFinite);
2381
- await Promise.all(tabIds.map((id) => waitForSettle2(id, waitTimeoutMs)));
2382
- }
2383
- const snapshot = await deps2.getTabSnapshot();
2384
- const selection = deps2.selectTabsByScope(snapshot, params);
2385
- if (selection.error) {
2386
- throw selection.error;
2387
- }
2388
- const tabs3 = selection.tabs;
2389
- const selectorSpecs = [];
2390
- if (Array.isArray(params.selectorSpecs)) {
2391
- selectorSpecs.push(...params.selectorSpecs);
2392
- }
2393
- if (params.signalConfig && typeof params.signalConfig === "object") {
2394
- const config = params.signalConfig;
2395
- if (Array.isArray(config.selectors)) {
2396
- selectorSpecs.push(...config.selectors);
2397
- }
2398
- if (config.signals && typeof config.signals === "object") {
2399
- const signals = config.signals;
2400
- const selectorConfig = signals.selector;
2401
- if (selectorConfig && Array.isArray(selectorConfig.selectors)) {
2402
- selectorSpecs.push(...selectorConfig.selectors);
2403
- }
2404
- }
2405
- }
2406
- const normalizedSelectors = selectorSpecs.filter((spec) => spec && typeof spec.selector === "string" && spec.selector.length > 0).map((spec) => {
2407
- const selector = spec.selector;
2408
- return {
2409
- name: typeof spec.name === "string" ? spec.name : void 0,
2410
- selector,
2411
- attr: typeof spec.attr === "string" ? spec.attr : "text",
2412
- all: Boolean(spec.all),
2413
- text: typeof spec.text === "string" && spec.text.trim() ? spec.text.trim() : void 0,
2414
- textMode: typeof spec.textMode === "string" ? spec.textMode.trim().toLowerCase() : void 0
2415
- };
2416
- });
2417
- const selectorWarnings = normalizedSelectors.filter((spec) => spec.selector.includes(":contains(")).map((spec) => ({
2418
- code: "unsupported_selector_syntax",
2419
- signalId: "selector",
2420
- selector: spec.selector,
2421
- name: spec.name || spec.selector,
2422
- message: "Selector uses unsupported CSS :contains() syntax.",
2423
- hint: "Use selector text filters (text/textMode) or a different selector."
2424
- }));
2425
- const signalDefs = [];
2426
- for (const signalId of signalList) {
2427
- if (signalId === "page-meta") {
2428
- signalDefs.push({
2429
- id: signalId,
2430
- match: (tab) => isScriptableUrl2(tab.url),
2431
- run: async (tabId) => extractPageMeta2(tabId, signalTimeoutMs, exports.DESCRIPTION_MAX_LENGTH)
2432
- });
2433
- } else if (signalId === "selector") {
2434
- signalDefs.push({
2435
- id: signalId,
2436
- match: (tab) => isScriptableUrl2(tab.url),
2437
- run: async (tabId) => extractSelectorSignal2(tabId, normalizedSelectors, signalTimeoutMs, exports.SELECTOR_VALUE_MAX_LENGTH)
2438
- });
2439
- }
2440
- }
2441
- const tasks = [];
2442
- for (const tab of tabs3) {
2443
- for (const signal of signalDefs) {
2444
- if (signal.match(tab)) {
2445
- tasks.push({ tab, signal });
2446
- }
2447
- }
2448
- }
2449
- const totalTasks = tasks.length;
2450
- let completedTasks = 0;
2451
- const entryMap = /* @__PURE__ */ new Map();
2452
- const workerCount = Math.min(signalConcurrency, totalTasks || 1);
2453
- let index = 0;
2454
- const workers = Array.from({ length: workerCount }, async () => {
2455
- while (true) {
2456
- const currentIndex = index;
2457
- if (currentIndex >= totalTasks) {
2458
- return;
2459
- }
2460
- index += 1;
2461
- const task = tasks[currentIndex];
2462
- const tabId = task.tab.tabId;
2463
- let result = null;
2464
- let error = null;
2465
- try {
2466
- await waitForTabReady2(tabId, params, signalTimeoutMs);
2467
- result = await task.signal.run(tabId);
2468
- if (task.signal.id === "selector" && result && typeof result === "object") {
2469
- const selectorErrors = Object.keys(result.errors || {});
2470
- if (selectorErrors.length > 0) {
2471
- error = `selector failures: ${selectorErrors.join(", ")}`;
2472
- }
2473
- }
2474
- } catch (err) {
2475
- const message = err instanceof Error ? err.message : "signal_error";
2476
- error = message;
2477
- }
2478
- const entry = entryMap.get(tabId) || { tab: task.tab, signals: {} };
2479
- entry.signals[task.signal.id] = {
2480
- ok: error === null,
2481
- data: result,
2482
- error
2483
- };
2484
- entryMap.set(tabId, entry);
2485
- completedTasks += 1;
2486
- if (progressEnabled) {
2487
- deps2.sendProgress(requestId, {
2488
- phase: "inspect",
2489
- processed: completedTasks,
2490
- total: totalTasks,
2491
- signalId: task.signal.id,
2492
- tabId
2493
- });
2494
- }
2495
- }
2496
- });
2497
- await Promise.all(workers);
2498
- const entries = Array.from(entryMap.values()).map((entry) => ({
2499
- tabId: entry.tab.tabId,
2500
- windowId: entry.tab.windowId,
2501
- groupId: entry.tab.groupId,
2502
- url: entry.tab.url,
2503
- title: entry.tab.title,
2504
- signals: entry.signals
2505
- }));
2506
- const response = {
2507
- totals: {
2508
- tabs: tabs3.length,
2509
- signals: signalDefs.length,
2510
- tasks: totalTasks
2511
- },
2512
- entries
2513
- };
2514
- if (selectorWarnings.length > 0) {
2515
- response.warnings = selectorWarnings;
2516
- }
2517
- return response;
2518
- }
2519
- }
2520
- });
2521
-
2522
- // dist/extension/lib/undo-handlers.js
2523
- var require_undo_handlers = __commonJS({
2524
- "dist/extension/lib/undo-handlers.js"(exports) {
2525
- "use strict";
2526
- Object.defineProperty(exports, "__esModule", { value: true });
2527
- exports.undoGroupUpdate = undoGroupUpdate;
2528
- exports.undoGroupUngroup = undoGroupUngroup;
2529
- exports.undoGroupAssign = undoGroupAssign;
2530
- exports.undoGroupGather = undoGroupGather;
2531
- exports.undoMoveTab = undoMoveTab;
2532
- exports.undoMoveGroup = undoMoveGroup;
2533
- exports.undoMergeWindow = undoMergeWindow;
2534
- exports.undoArchive = undoArchive;
2535
- exports.undoClose = undoClose;
2536
- exports.undoTransaction = undoTransaction;
2537
- async function ensureWindow(windowId) {
2538
- if (windowId) {
2539
- try {
2540
- const existing = await chrome.windows.get(windowId);
2541
- if (existing) {
2542
- return windowId;
2543
- }
2544
- } catch {
2545
- }
2546
- }
2547
- const created = await chrome.windows.create({ focused: false });
2548
- return created.id;
2549
- }
2550
- async function restoreTabsFromUndo(entries, deps2) {
2551
- const skipped = [];
2552
- const restored = [];
2553
- const windowMap = /* @__PURE__ */ new Map();
2554
- for (const entry of entries) {
2555
- const tabId = Number(entry.tabId);
2556
- if (!Number.isFinite(tabId)) {
2557
- skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
2558
- continue;
2559
- }
2560
- const sourceWindowId = Number(entry.windowId);
2561
- if (!Number.isFinite(sourceWindowId)) {
2562
- skipped.push({ tabId, reason: "missing_window" });
2563
- continue;
2564
- }
2565
- let targetWindowId = windowMap.get(sourceWindowId);
2566
- if (!targetWindowId) {
2567
- targetWindowId = await ensureWindow(sourceWindowId);
2568
- windowMap.set(sourceWindowId, targetWindowId);
2569
- }
2570
- try {
2571
- await chrome.tabs.move(tabId, { windowId: targetWindowId, index: -1 });
2572
- restored.push({ tabId, entry, targetWindowId });
2573
- } catch {
2574
- skipped.push({ tabId, reason: "move_failed" });
2575
- }
2576
- }
2577
- const groupsByWindow = /* @__PURE__ */ new Map();
2578
- for (const item of restored) {
2579
- const entry = item.entry;
2580
- const rawGroupId = typeof entry.groupId === "number" ? entry.groupId : null;
2581
- const groupId = rawGroupId != null && rawGroupId !== -1 ? rawGroupId : null;
2582
- const groupTitle = typeof entry.groupTitle === "string" ? entry.groupTitle : null;
2583
- if (!groupId && !groupTitle) {
2584
- continue;
2585
- }
2586
- const groupKey = groupId != null ? `id:${groupId}` : `title:${groupTitle}`;
2587
- const key = `${item.targetWindowId}:${groupKey}`;
2588
- if (!groupsByWindow.has(key)) {
2589
- groupsByWindow.set(key, {
2590
- windowId: item.targetWindowId,
2591
- groupId,
2592
- groupTitle,
2593
- groupColor: typeof entry.groupColor === "string" ? entry.groupColor : null,
2594
- groupCollapsed: typeof entry.groupCollapsed === "boolean" ? entry.groupCollapsed : null,
2595
- tabIds: []
2596
- });
2597
- }
2598
- groupsByWindow.get(key)?.tabIds.push(item.tabId);
2599
- }
2600
- for (const group of groupsByWindow.values()) {
2601
- if (group.tabIds.length === 0) {
2602
- continue;
2603
- }
2604
- let targetGroupId = null;
2605
- if (group.groupId != null) {
2606
- try {
2607
- targetGroupId = await chrome.tabs.group({ groupId: group.groupId, tabIds: group.tabIds });
2608
- } catch {
2609
- targetGroupId = null;
2610
- }
2611
- }
2612
- if (targetGroupId == null) {
2613
- try {
2614
- targetGroupId = await chrome.tabs.group({
2615
- tabIds: group.tabIds,
2616
- createProperties: { windowId: group.windowId }
2617
- });
2618
- } catch (error) {
2619
- deps2.log("Failed to regroup tabs", error);
2620
- continue;
2621
- }
2622
- }
2623
- const update = {};
2624
- if (typeof group.groupTitle === "string") {
2625
- update.title = group.groupTitle;
2626
- }
2627
- if (typeof group.groupColor === "string" && group.groupColor) {
2628
- update.color = group.groupColor;
2629
- }
2630
- if (typeof group.groupCollapsed === "boolean") {
2631
- update.collapsed = group.groupCollapsed;
2632
- }
2633
- if (Object.keys(update).length > 0) {
2634
- try {
2635
- await chrome.tabGroups.update(targetGroupId, update);
2636
- } catch (error) {
2637
- deps2.log("Failed to update restored group", error);
2638
- }
2639
- }
2640
- }
2641
- const orderByWindow = /* @__PURE__ */ new Map();
2642
- for (const item of restored) {
2643
- const index = Number(item.entry.index);
2644
- if (!Number.isFinite(index)) {
2645
- continue;
2646
- }
2647
- if (!orderByWindow.has(item.targetWindowId)) {
2648
- orderByWindow.set(item.targetWindowId, []);
2649
- }
2650
- orderByWindow.get(item.targetWindowId)?.push({ tabId: item.tabId, index });
2651
- }
2652
- for (const [targetWindowId, items] of orderByWindow.entries()) {
2653
- const ordered = [...items].sort((a, b) => a.index - b.index);
2654
- for (const item of ordered) {
2655
- try {
2656
- await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.index });
2657
- } catch {
2658
- }
2659
- }
2660
- }
2661
- return {
2662
- summary: {
2663
- restoredTabs: restored.length,
2664
- skippedTabs: skipped.length
2665
- },
2666
- skipped
2667
- };
2668
- }
2669
- async function undoGroupUpdate(undo) {
2670
- const groupId = Number(undo.groupId);
2671
- if (!Number.isFinite(groupId)) {
2672
- return {
2673
- summary: { restoredGroups: 0, skippedGroups: 1 },
2674
- skipped: [{ groupId: undo.groupId, reason: "missing_group" }]
2675
- };
2676
- }
2677
- const previous = undo.previous || {};
2678
- const update = {};
2679
- if (typeof previous.title === "string") {
2680
- update.title = previous.title;
2681
- }
2682
- if (typeof previous.color === "string" && previous.color) {
2683
- update.color = previous.color;
2684
- }
2685
- if (typeof previous.collapsed === "boolean") {
2686
- update.collapsed = previous.collapsed;
2687
- }
2688
- if (!Object.keys(update).length) {
2689
- return {
2690
- summary: { restoredGroups: 0, skippedGroups: 1 },
2691
- skipped: [{ groupId, reason: "missing_values" }]
2692
- };
2693
- }
2694
- try {
2695
- await chrome.tabGroups.update(groupId, update);
2696
- return {
2697
- summary: { restoredGroups: 1, skippedGroups: 0 },
2698
- skipped: []
2699
- };
2700
- } catch {
2701
- return {
2702
- summary: { restoredGroups: 0, skippedGroups: 1 },
2703
- skipped: [{ groupId, reason: "update_failed" }]
2704
- };
2705
- }
2706
- }
2707
- async function undoGroupUngroup(undo, deps2) {
2708
- const tabs2 = undo.tabs || [];
2709
- return await restoreTabsFromUndo(tabs2, deps2);
2710
- }
2711
- async function undoGroupAssign(undo, deps2) {
2712
- const tabs2 = undo.tabs || [];
2713
- return await restoreTabsFromUndo(tabs2, deps2);
2714
- }
2715
- async function undoGroupGather(undo, deps2) {
2716
- const tabs2 = undo.tabs || [];
2717
- return await restoreTabsFromUndo(tabs2, deps2);
2718
- }
2719
- async function undoMoveTab(undo, deps2) {
2720
- const from = undo.from || {};
2721
- const entry = {
2722
- tabId: undo.tabId,
2723
- windowId: from.windowId,
2724
- index: from.index,
2725
- groupId: from.groupId,
2726
- groupTitle: from.groupTitle,
2727
- groupColor: from.groupColor,
2728
- groupCollapsed: from.groupCollapsed
2729
- };
2730
- return await restoreTabsFromUndo([entry], deps2);
2731
- }
2732
- async function undoMoveGroup(undo, deps2) {
2733
- const tabs2 = undo.tabs || [];
2734
- return await restoreTabsFromUndo(tabs2, deps2);
2735
- }
2736
- async function undoMergeWindow(undo, deps2) {
2737
- const tabs2 = undo.tabs || [];
2738
- return await restoreTabsFromUndo(tabs2, deps2);
2739
- }
2740
- async function undoArchive(undo, deps2) {
2741
- const tabs2 = undo.tabs || [];
2742
- const restored = [];
2743
- const skipped = [];
2744
- const windowMap = /* @__PURE__ */ new Map();
2745
- for (const entry of tabs2) {
2746
- if (!entry.tabId) {
2747
- skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
2748
- continue;
2749
- }
2750
- let targetWindowId = windowMap.get(entry.from.windowId);
2751
- if (!targetWindowId) {
2752
- targetWindowId = await ensureWindow(entry.from.windowId);
2753
- windowMap.set(entry.from.windowId, targetWindowId);
2754
- }
2755
- try {
2756
- await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: -1 });
2757
- restored.push({ tabId: entry.tabId, targetWindowId });
2758
- } catch {
2759
- skipped.push({ tabId: entry.tabId, reason: "move_failed" });
2760
- }
2761
- }
2762
- const restoredSet = new Set(restored.map((item) => item.tabId));
2763
- const groupsByWindow = /* @__PURE__ */ new Map();
2764
- for (const entry of tabs2) {
2765
- if (!restoredSet.has(entry.tabId)) {
2766
- continue;
2767
- }
2768
- const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
2769
- const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
2770
- const hasGroupTitle = entry.from.groupTitle != null;
2771
- if (!hasGroupId && !hasGroupTitle) {
2772
- continue;
2773
- }
2774
- const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
2775
- const key = `${targetWindowId}:${groupKey}`;
2776
- if (!groupsByWindow.has(key)) {
2777
- groupsByWindow.set(key, []);
2778
- }
2779
- groupsByWindow.get(key)?.push(entry);
2780
- }
2781
- for (const [key, groupTabs] of groupsByWindow.entries()) {
2782
- const [windowIdPart] = key.split(":");
2783
- const targetWindowId = Number(windowIdPart);
2784
- const tabIds = groupTabs.map((entry) => entry.tabId).filter(Boolean);
2785
- if (!tabIds.length) {
2786
- continue;
2787
- }
2788
- try {
2789
- const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
2790
- await chrome.tabGroups.update(groupId, {
2791
- title: groupTabs[0].from.groupTitle || "",
2792
- color: groupTabs[0].from.groupColor || "grey",
2793
- collapsed: groupTabs[0].from.groupCollapsed || false
2794
- });
2795
- } catch (error) {
2796
- deps2.log("Failed to recreate group", error);
2797
- }
2798
- }
2799
- for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
2800
- const windowTabs = tabs2.filter((entry) => entry.from.windowId === originalWindowId && restoredSet.has(entry.tabId)).sort((a, b) => a.from.index - b.from.index);
2801
- for (const entry of windowTabs) {
2802
- if (!entry.tabId) {
2803
- continue;
2804
- }
2805
- try {
2806
- await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: entry.from.index });
2807
- } catch {
2808
- }
2809
- }
2810
- const activeTab = windowTabs.find((entry) => entry.active);
2811
- if (activeTab && activeTab.tabId) {
2812
- try {
2813
- await chrome.tabs.update(activeTab.tabId, { active: true });
2814
- } catch {
2815
- }
2816
- }
2817
- }
2818
- return {
2819
- summary: {
2820
- restoredTabs: restored.length,
2821
- skippedTabs: skipped.length
2822
- },
2823
- skipped
2824
- };
2825
- }
2826
- async function undoClose(undo, deps2) {
2827
- const tabs2 = undo.tabs || [];
2828
- const restored = [];
2829
- const skipped = [];
2830
- const windowMap = /* @__PURE__ */ new Map();
2831
- for (const entry of tabs2) {
2832
- if (!entry.url) {
2833
- skipped.push({ url: entry.url, reason: "missing_url" });
2834
- continue;
2835
- }
2836
- let targetWindowId = windowMap.get(entry.from.windowId);
2837
- if (!targetWindowId) {
2838
- targetWindowId = await ensureWindow(entry.from.windowId);
2839
- windowMap.set(entry.from.windowId, targetWindowId);
2840
- }
2841
- try {
2842
- const created = await chrome.tabs.create({
2843
- windowId: targetWindowId,
2844
- url: entry.url,
2845
- active: false,
2846
- pinned: entry.pinned
2847
- });
2848
- restored.push({ tabId: created.id, entry });
2849
- } catch {
2850
- skipped.push({ url: entry.url, reason: "create_failed" });
2851
- }
2852
- }
2853
- const groupsByWindow = /* @__PURE__ */ new Map();
2854
- for (const item of restored) {
2855
- const entry = item.entry;
2856
- const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
2857
- const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
2858
- const hasGroupTitle = entry.from.groupTitle != null;
2859
- if (!hasGroupId && !hasGroupTitle) {
2860
- continue;
2861
- }
2862
- const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
2863
- const key = `${targetWindowId}:${groupKey}`;
2864
- if (!groupsByWindow.has(key)) {
2865
- groupsByWindow.set(key, []);
2866
- }
2867
- groupsByWindow.get(key)?.push({ tabId: item.tabId, entry });
2868
- }
2869
- for (const [key, groupTabs] of groupsByWindow.entries()) {
2870
- const [windowIdPart] = key.split(":");
2871
- const targetWindowId = Number(windowIdPart);
2872
- const tabIds = groupTabs.map((item) => item.tabId).filter(Boolean);
2873
- if (!tabIds.length) {
2874
- continue;
2875
- }
2876
- try {
2877
- const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
2878
- await chrome.tabGroups.update(groupId, {
2879
- title: groupTabs[0].entry.from.groupTitle || "",
2880
- color: groupTabs[0].entry.from.groupColor || "grey",
2881
- collapsed: groupTabs[0].entry.from.groupCollapsed || false
2882
- });
2883
- } catch (error) {
2884
- deps2.log("Failed to recreate group", error);
2885
- }
2886
- }
2887
- for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
2888
- const windowTabs = restored.map((item) => ({ tabId: item.tabId, entry: item.entry })).filter((item) => item.entry.from.windowId === originalWindowId).sort((a, b) => a.entry.from.index - b.entry.from.index);
2889
- for (const item of windowTabs) {
2890
- try {
2891
- await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.entry.from.index });
2892
- } catch {
2893
- }
2894
- }
2895
- const activeTab = windowTabs.find((item) => item.entry.active);
2896
- if (activeTab) {
2897
- try {
2898
- await chrome.tabs.update(activeTab.tabId, { active: true });
2899
- } catch {
2900
- }
2901
- }
2902
- }
2903
- return {
2904
- summary: {
2905
- restoredTabs: restored.length,
2906
- skippedTabs: skipped.length
2907
- },
2908
- skipped
2909
- };
2910
- }
2911
- async function undoTransaction(params, deps2) {
2912
- if (!params.record || !params.record.undo) {
2913
- throw new Error("Undo record missing");
2914
- }
2915
- const undo = params.record.undo;
2916
- if (undo.action === "archive") {
2917
- return await undoArchive(undo, deps2);
2918
- }
2919
- if (undo.action === "close") {
2920
- return await undoClose(undo, deps2);
2921
- }
2922
- if (undo.action === "group-update") {
2923
- return await undoGroupUpdate(undo);
2924
- }
2925
- if (undo.action === "group-ungroup") {
2926
- return await undoGroupUngroup(undo, deps2);
2927
- }
2928
- if (undo.action === "group-assign") {
2929
- return await undoGroupAssign(undo, deps2);
2930
- }
2931
- if (undo.action === "group-gather") {
2932
- return await undoGroupGather(undo, deps2);
2933
- }
2934
- if (undo.action === "move-tab") {
2935
- return await undoMoveTab(undo, deps2);
2936
- }
2937
- if (undo.action === "move-group") {
2938
- return await undoMoveGroup(undo, deps2);
2939
- }
2940
- if (undo.action === "merge-window") {
2941
- return await undoMergeWindow(undo, deps2);
2942
- }
2943
- throw new Error(`Unknown undo action: ${undo.action}`);
2944
- }
2945
- }
2946
- });
2947
-
2948
- // dist/extension/lib/archive.js
2949
- var require_archive = __commonJS({
2950
- "dist/extension/lib/archive.js"(exports) {
2951
- "use strict";
2952
- Object.defineProperty(exports, "__esModule", { value: true });
2953
- exports.ensureArchiveWindow = ensureArchiveWindow;
2954
- exports.archiveTabs = archiveTabs;
2955
- exports.getTabsByIds = getTabsByIds;
2956
- exports.closeTabs = closeTabs;
2957
- exports.mergeWindow = mergeWindow;
2958
- async function ensureArchiveWindow(deps2) {
2959
- const archiveWindowId = await deps2.getArchiveWindowId();
2960
- if (archiveWindowId) {
2961
- try {
2962
- await chrome.windows.get(archiveWindowId);
2963
- return archiveWindowId;
2964
- } catch {
2965
- await deps2.setArchiveWindowId(null);
2966
- }
2967
- }
2968
- const created = await chrome.windows.create({ focused: false });
2969
- await deps2.setArchiveWindowId(created.id);
2970
- return created.id;
2971
- }
2972
- async function archiveTabs(params, deps2) {
2973
- const snapshot = await deps2.getTabSnapshot();
2974
- const windowLabels = deps2.buildWindowLabels(snapshot);
2975
- let windowsToProcess = snapshot.windows;
2976
- if (params.windowId) {
2977
- const resolvedWindowId = deps2.resolveWindowIdFromParams(snapshot, params.windowId);
2978
- windowsToProcess = resolvedWindowId != null ? windowsToProcess.filter((win) => win.windowId === resolvedWindowId) : [];
2979
- } else if (!params.all) {
2980
- const focused = windowsToProcess.find((win) => win.focused);
2981
- windowsToProcess = focused ? [focused] : [];
2982
- }
2983
- if (params.groupTitle || params.groupId || params.tabIds) {
2984
- const selected = deps2.selectTabsByScope(snapshot, params);
2985
- if (selected.error) {
2986
- throw selected.error;
2987
- }
2988
- windowsToProcess = snapshot.windows.map((win) => ({
2989
- windowId: win.windowId,
2990
- focused: win.focused,
2991
- state: win.state,
2992
- tabs: win.tabs.filter((tab) => selected.tabs.some((sel) => sel.tabId === tab.tabId)),
2993
- groups: win.groups
2994
- })).filter((win) => win.tabs.length > 0);
2995
- }
2996
- if (windowsToProcess.length === 0) {
2997
- const fullResult2 = {
2998
- txid: params.txid || null,
2999
- summary: { movedTabs: 0, movedGroups: 0, skippedTabs: 0 },
3000
- archiveWindowId: null,
3001
- skipped: [],
3002
- undo: { action: "archive", tabs: [] }
3003
- };
3004
- return fullResult2;
3005
- }
3006
- const archiveWindowId = await ensureArchiveWindow(deps2);
3007
- const undoTabs = [];
3008
- const skipped = [];
3009
- let movedGroups = 0;
3010
- let movedTabs = 0;
3011
- for (const window2 of windowsToProcess) {
3012
- const groupsById = /* @__PURE__ */ new Map();
3013
- for (const group of window2.groups) {
3014
- groupsById.set(group.groupId, group);
3015
- }
3016
- const groupedTabs = /* @__PURE__ */ new Map();
3017
- const ungroupedTabs = [];
3018
- for (const tab of window2.tabs) {
3019
- if (tab.tabId == null) {
3020
- continue;
3021
- }
3022
- if (tab.groupId === -1) {
3023
- ungroupedTabs.push(tab);
3024
- } else {
3025
- if (!groupedTabs.has(tab.groupId)) {
3026
- groupedTabs.set(tab.groupId, []);
3027
- }
3028
- groupedTabs.get(tab.groupId)?.push(tab);
3029
- }
3030
- }
3031
- const windowLabel = windowLabels.get(window2.windowId) || `W${window2.windowId}`;
3032
- const plans = [];
3033
- for (const [groupId, tabs2] of groupedTabs.entries()) {
3034
- const group = groupsById.get(groupId) || null;
3035
- plans.push({
3036
- windowId: window2.windowId,
3037
- windowLabel,
3038
- group,
3039
- tabs: tabs2,
3040
- isUngrouped: false
3041
- });
3042
- }
3043
- if (ungroupedTabs.length > 0) {
3044
- plans.push({
3045
- windowId: window2.windowId,
3046
- windowLabel,
3047
- group: null,
3048
- tabs: ungroupedTabs,
3049
- isUngrouped: true
3050
- });
3051
- }
3052
- for (const plan of plans) {
3053
- const tabIds = plan.tabs.map((tab) => tab.tabId);
3054
- if (tabIds.length === 0) {
3055
- continue;
3056
- }
3057
- const tabById = /* @__PURE__ */ new Map();
3058
- for (const tab of plan.tabs) {
3059
- tabById.set(tab.tabId, tab);
3060
- }
3061
- let moved;
3062
- try {
3063
- moved = await chrome.tabs.move(tabIds, { windowId: archiveWindowId, index: -1 });
3064
- } catch (error) {
3065
- for (const tabId of tabIds) {
3066
- skipped.push({ tabId, reason: "move_failed" });
3067
- }
3068
- deps2.log("Failed to move tabs", error);
3069
- continue;
3070
- }
3071
- const movedList = Array.isArray(moved) ? moved : [moved];
3072
- const movedIds = movedList.map((tab) => tab.id);
3073
- movedTabs += movedIds.length;
3074
- for (const movedId of movedIds) {
3075
- const tab = tabById.get(movedId);
3076
- if (!tab) {
3077
- continue;
3078
- }
3079
- undoTabs.push({
3080
- tabId: tab.tabId,
3081
- url: tab.url,
3082
- title: tab.title,
3083
- pinned: tab.pinned,
3084
- active: tab.active,
3085
- from: {
3086
- windowId: tab.windowId,
3087
- index: tab.index,
3088
- groupId: tab.groupId,
3089
- groupTitle: plan.group ? plan.group.title : null,
3090
- groupColor: plan.group ? plan.group.color : null,
3091
- groupCollapsed: plan.group ? plan.group.collapsed : null
3092
- }
3093
- });
3094
- }
3095
- const titleBase = plan.group && plan.group.title ? plan.group.title : plan.isUngrouped ? "Ungrouped" : "Group";
3096
- const archiveTitle = `${plan.windowLabel} - ${titleBase}`;
3097
- const groupColor = plan.group && plan.group.color ? plan.group.color : "grey";
3098
- try {
3099
- const newGroupId = await chrome.tabs.group({ tabIds: movedIds, createProperties: { windowId: archiveWindowId } });
3100
- await chrome.tabGroups.update(newGroupId, { title: archiveTitle, color: groupColor });
3101
- movedGroups += 1;
3102
- } catch (error) {
3103
- deps2.log("Failed to group archived tabs", error);
3104
- }
3105
- }
3106
- }
3107
- const fullResult = {
3108
- txid: params.txid || null,
3109
- summary: {
3110
- movedTabs,
3111
- movedGroups,
3112
- skippedTabs: skipped.length
3113
- },
3114
- archiveWindowId,
3115
- skipped,
3116
- undo: {
3117
- action: "archive",
3118
- tabs: undoTabs
3119
- }
3120
- };
3121
- return {
3122
- txid: fullResult.txid,
3123
- summary: fullResult.summary,
3124
- archiveWindowId,
3125
- skipped: fullResult.skipped,
3126
- undo: fullResult.undo
3127
- };
3128
- }
3129
- async function getTabsByIds(tabIds) {
3130
- const results = [];
3131
- for (const tabId of tabIds) {
3132
- try {
3133
- const tab = await chrome.tabs.get(tabId);
3134
- results.push(tab);
3135
- } catch {
3136
- results.push(null);
3137
- }
3138
- }
3139
- return results;
3140
- }
3141
- async function closeTabs(params, deps2) {
3142
- const mode = params.mode || "direct";
3143
- if (mode === "direct" && !params.confirmed) {
3144
- throw new Error("Direct close requires confirmation");
3145
- }
3146
- let tabIds = params.tabIds || [];
3147
- if (!tabIds.length && (params.groupTitle || params.groupId || params.windowId)) {
3148
- const snapshot = await deps2.getTabSnapshot();
3149
- const selection = deps2.selectTabsByScope(snapshot, params);
3150
- if (selection.error) {
3151
- throw selection.error;
3152
- }
3153
- tabIds = selection.tabs.map((tab) => tab.tabId);
3154
- }
3155
- if (!tabIds.length) {
3156
- const fullResult2 = {
3157
- txid: params.txid || null,
3158
- summary: { closedTabs: 0, skippedTabs: 0 },
3159
- skipped: [],
3160
- undo: { action: "close", tabs: [] }
3161
- };
3162
- return fullResult2;
3163
- }
3164
- const expectedUrls = params.expectedUrls || {};
3165
- const tabInfos = await getTabsByIds(tabIds);
3166
- const validTabs = [];
3167
- const skipped = [];
3168
- const groups2 = await chrome.tabGroups.query({});
3169
- const groupById = new Map(groups2.map((group) => [group.id, group]));
3170
- for (let i = 0; i < tabIds.length; i += 1) {
3171
- const tabId = tabIds[i];
3172
- const tab = tabInfos[i];
3173
- if (!tab) {
3174
- skipped.push({ tabId, reason: "not_found" });
3175
- continue;
3176
- }
3177
- const expected = expectedUrls[String(tabId)];
3178
- if (expected && tab.url !== expected) {
3179
- skipped.push({ tabId, reason: "url_mismatch" });
3180
- continue;
3181
- }
3182
- const group = tab.groupId !== -1 ? groupById.get(tab.groupId) : null;
3183
- validTabs.push({
3184
- tabId,
3185
- url: tab.url,
3186
- title: tab.title,
3187
- pinned: tab.pinned,
3188
- active: tab.active,
3189
- from: {
3190
- windowId: tab.windowId,
3191
- index: tab.index,
3192
- groupId: tab.groupId,
3193
- groupTitle: group ? group.title : null,
3194
- groupColor: group ? group.color : null,
3195
- groupCollapsed: group ? group.collapsed : null
3196
- }
3197
- });
3198
- }
3199
- if (validTabs.length > 0) {
3200
- await chrome.tabs.remove(validTabs.map((tab) => tab.tabId));
3201
- }
3202
- const fullResult = {
3203
- txid: params.txid || null,
3204
- summary: {
3205
- closedTabs: validTabs.length,
3206
- skippedTabs: skipped.length
3207
- },
3208
- skipped,
3209
- undo: {
3210
- action: "close",
3211
- tabs: validTabs.map((tab) => ({
3212
- url: tab.url,
3213
- title: tab.title,
3214
- pinned: tab.pinned,
3215
- active: tab.active,
3216
- from: tab.from
3217
- }))
3218
- }
3219
- };
3220
- return {
3221
- txid: fullResult.txid,
3222
- summary: fullResult.summary,
3223
- skipped: fullResult.skipped,
3224
- undo: fullResult.undo
3225
- };
3226
- }
3227
- async function mergeWindow(params, deps2) {
3228
- const fromWindowId = Number.isFinite(params.fromWindowId) ? Number(params.fromWindowId) : Number(params.windowId);
3229
- const toWindowId = Number.isFinite(params.toWindowId) ? Number(params.toWindowId) : null;
3230
- if (!Number.isFinite(fromWindowId) || !Number.isFinite(toWindowId)) {
3231
- throw new Error("Missing source or target window id");
3232
- }
3233
- if (fromWindowId === toWindowId) {
3234
- throw new Error("Source and target windows must differ");
3235
- }
3236
- const snapshot = await deps2.getTabSnapshot();
3237
- const windows = snapshot.windows;
3238
- const sourceWindow = windows.find((win) => win.windowId === fromWindowId);
3239
- if (!sourceWindow) {
3240
- throw new Error("Source window not found");
3241
- }
3242
- const targetWindow = windows.find((win) => win.windowId === toWindowId);
3243
- if (!targetWindow) {
3244
- throw new Error("Target window not found");
3245
- }
3246
- const rawTabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
3247
- const tabIdSet = new Set(rawTabIds.filter((id) => Number.isFinite(id)));
3248
- const skipped = [];
3249
- let selectedTabs = sourceWindow.tabs;
3250
- if (tabIdSet.size > 0) {
3251
- const sourceTabIds = new Set(sourceWindow.tabs.map((tab) => tab.tabId).filter((id) => typeof id === "number"));
3252
- for (const tabId of tabIdSet) {
3253
- if (!sourceTabIds.has(tabId)) {
3254
- skipped.push({ tabId, reason: "not_in_source" });
3255
- }
3256
- }
3257
- selectedTabs = sourceWindow.tabs.filter((tab) => tabIdSet.has(tab.tabId));
3258
- }
3259
- if (selectedTabs.length === 0) {
3260
- const fullResult2 = {
3261
- fromWindowId,
3262
- toWindowId,
3263
- summary: { movedTabs: 0, movedGroups: 0, skippedTabs: skipped.length, closedSource: false },
3264
- skipped,
3265
- groups: [],
3266
- undo: {
3267
- action: "merge-window",
3268
- fromWindowId,
3269
- toWindowId,
3270
- closedSource: false,
3271
- tabs: []
3272
- }
3273
- };
3274
- return fullResult2;
3275
- }
3276
- const orderedTabs = [...selectedTabs].sort((a, b) => {
3277
- const aIndex = Number(a.index);
3278
- const bIndex = Number(b.index);
3279
- if (!Number.isFinite(aIndex) && !Number.isFinite(bIndex)) {
3280
- return 0;
3281
- }
3282
- if (!Number.isFinite(aIndex)) {
3283
- return 1;
3284
- }
3285
- if (!Number.isFinite(bIndex)) {
3286
- return -1;
3287
- }
3288
- return aIndex - bIndex;
3289
- });
3290
- const groupById = /* @__PURE__ */ new Map();
3291
- for (const group of sourceWindow.groups) {
3292
- groupById.set(group.groupId, group);
3293
- }
3294
- const plans = [];
3295
- let currentPlan = null;
3296
- for (const tab of orderedTabs) {
3297
- const rawGroupId = tab.groupId;
3298
- const groupId = typeof rawGroupId === "number" && rawGroupId !== -1 ? rawGroupId : null;
3299
- if (!currentPlan || currentPlan.groupId !== groupId) {
3300
- currentPlan = { groupId, tabs: [] };
3301
- plans.push(currentPlan);
3302
- }
3303
- currentPlan.tabs.push(tab);
3304
- }
3305
- let movedTabs = 0;
3306
- let movedGroups = 0;
3307
- const groups2 = [];
3308
- const undoTabs = [];
3309
- for (const plan of plans) {
3310
- const tabIds = plan.tabs.map((tab) => tab.tabId).filter((id) => typeof id === "number");
3311
- if (!tabIds.length) {
3312
- continue;
3313
- }
3314
- let moved;
3315
- try {
3316
- moved = await chrome.tabs.move(tabIds, { windowId: toWindowId, index: -1 });
3317
- } catch (error) {
3318
- for (const tabId of tabIds) {
3319
- skipped.push({ tabId, reason: "move_failed" });
3320
- }
3321
- deps2.log("Failed to move tabs", error);
3322
- continue;
3323
- }
3324
- const movedList = Array.isArray(moved) ? moved : [moved];
3325
- const movedIds = movedList.map((tab) => tab.id).filter((id) => typeof id === "number");
3326
- movedTabs += movedIds.length;
3327
- for (const entry of plan.tabs) {
3328
- if (typeof entry.tabId !== "number") {
3329
- continue;
3330
- }
3331
- const meta = groupById.get(entry.groupId);
3332
- undoTabs.push({
3333
- tabId: entry.tabId,
3334
- windowId: entry.windowId,
3335
- index: entry.index,
3336
- groupId: entry.groupId,
3337
- groupTitle: entry.groupTitle,
3338
- groupColor: entry.groupColor,
3339
- groupCollapsed: meta ? meta.collapsed : null
3340
- });
3341
- }
3342
- if (plan.groupId != null && movedIds.length > 0) {
3343
- movedGroups += 1;
3344
- let newGroupId = null;
3345
- try {
3346
- newGroupId = await chrome.tabs.group({ tabIds: movedIds, createProperties: { windowId: toWindowId } });
3347
- const meta = groupById.get(plan.groupId);
3348
- if (meta) {
3349
- await chrome.tabGroups.update(newGroupId, {
3350
- title: meta.title || "",
3351
- color: meta.color || "grey",
3352
- collapsed: meta.collapsed || false
3353
- });
3354
- }
3355
- } catch (error) {
3356
- deps2.log("Failed to regroup tabs", error);
3357
- }
3358
- groups2.push({ sourceGroupId: plan.groupId, newGroupId });
3359
- }
3360
- }
3361
- let closedSource = false;
3362
- if (params.closeSource === true) {
3363
- try {
3364
- const remainingTabs = await chrome.tabs.query({ windowId: fromWindowId });
3365
- if (remainingTabs.length === 0) {
3366
- await chrome.windows.remove(fromWindowId);
3367
- closedSource = true;
3368
- }
3369
- } catch (error) {
3370
- deps2.log("Failed to close source window", error);
3371
- }
3372
- }
3373
- const fullResult = {
3374
- fromWindowId,
3375
- toWindowId,
3376
- summary: { movedTabs, movedGroups, skippedTabs: skipped.length, closedSource },
3377
- skipped,
3378
- groups: groups2,
3379
- undo: {
3380
- action: "merge-window",
3381
- fromWindowId,
3382
- toWindowId,
3383
- closedSource,
3384
- tabs: undoTabs
3385
- },
3386
- txid: params.txid || null
3387
- };
3388
- return {
3389
- fromWindowId,
3390
- toWindowId,
3391
- summary: fullResult.summary,
3392
- skipped: fullResult.skipped,
3393
- groups: fullResult.groups,
3394
- undo: fullResult.undo,
3395
- txid: fullResult.txid
3396
- };
3397
- }
3398
- }
3399
- });
3400
-
3401
- // dist/extension/background.js
3402
- var HOST_NAME = "com.erwinkroon.tabctl";
3403
- var manifest = chrome.runtime.getManifest();
3404
- var MANIFEST_VERSION = manifest.version || "0.0.0";
3405
- var MANIFEST_VERSION_NAME = manifest.version_name || MANIFEST_VERSION;
3406
- function parseVersionName(versionName) {
3407
- const match = versionName.match(/-dev\.([0-9a-f]+)(\.dirty)?$/i);
3408
- if (!match) {
3409
- return { gitSha: null, dirty: false };
3410
- }
3411
- return { gitSha: match[1] || null, dirty: Boolean(match[2]) };
3412
- }
3413
- var parsed = parseVersionName(MANIFEST_VERSION_NAME);
3414
- var VERSION_INFO = {
3415
- version: MANIFEST_VERSION_NAME,
3416
- baseVersion: MANIFEST_VERSION,
3417
- gitSha: parsed.gitSha,
3418
- dirty: parsed.dirty
3419
- };
3420
- var KEEPALIVE_ALARM = "tabctl-keepalive";
3421
- var KEEPALIVE_INTERVAL_MINUTES = 1;
3422
- var screenshot = require_screenshot();
3423
- var content = require_content();
3424
- var { delay, executeWithTimeout, isScriptableUrl, extractPageMeta, extractSelectorSignal, waitForTabLoad, waitForDomReady, waitForSettle, waitForTabReady, SETTLE_STABILITY_MS, SETTLE_POLL_INTERVAL_MS } = content;
3425
- var groups = require_groups();
3426
- var tabs = require_tabs();
3427
- var { getMostRecentFocusedWindowId, normalizeTabIndex } = tabs;
3428
- var move = require_move();
3429
- var inspect = require_inspect();
3430
- var { DESCRIPTION_MAX_LENGTH } = inspect;
3431
- var undoHandlers = require_undo_handlers();
3432
- var archive = require_archive();
3433
- var state = {
3434
- port: null,
3435
- archiveWindowId: null,
3436
- archiveWindowIdLoaded: false,
3437
- lastFocused: {},
3438
- lastFocusedLoaded: false
3439
- };
3440
- function log(...args) {
3441
- console.log("[tabctl]", ...args);
3442
- }
3443
- function sendResponse(id, ok, payload) {
3444
- if (!state.port) {
3445
- return;
3446
- }
3447
- if (ok) {
3448
- const data = typeof payload === "object" && payload !== null ? payload : { payload };
3449
- state.port.postMessage({ id, ok: true, data });
3450
- return;
3451
- }
3452
- const error = payload instanceof Error ? { message: payload.message, stack: payload.stack } : payload;
3453
- state.port.postMessage({ id, ok: false, error });
3454
- }
3455
- function connectNative() {
3456
- if (state.port) {
3457
- return;
3458
- }
3459
- try {
3460
- const port = chrome.runtime.connectNative(HOST_NAME);
3461
- state.port = port;
3462
- port.onMessage.addListener(handleNativeMessage);
3463
- port.onDisconnect.addListener(() => {
3464
- const lastError = chrome.runtime.lastError;
3465
- if (lastError) {
3466
- log("Native host disconnected:", lastError.message);
3467
- } else {
3468
- log("Native host disconnected");
3469
- }
3470
- state.port = null;
3471
- });
3472
- log("Native host connected");
3473
- } catch (error) {
3474
- log("Native host connection failed", error);
3475
- }
3476
- }
3477
- chrome.runtime.onInstalled.addListener(() => {
3478
- connectNative();
3479
- chrome.alarms.create(KEEPALIVE_ALARM, { periodInMinutes: KEEPALIVE_INTERVAL_MINUTES });
3480
- });
3481
- chrome.runtime.onStartup.addListener(() => {
3482
- connectNative();
3483
- chrome.alarms.create(KEEPALIVE_ALARM, { periodInMinutes: KEEPALIVE_INTERVAL_MINUTES });
3484
- });
3485
- chrome.alarms.onAlarm.addListener((alarm) => {
3486
- if (alarm.name === KEEPALIVE_ALARM) {
3487
- connectNative();
3488
- }
3489
- });
3490
- async function ensureLastFocusedLoaded() {
3491
- if (state.lastFocusedLoaded) {
3492
- return;
537
+ });
538
+ async function ensureLastFocusedLoaded() {
539
+ if (state.lastFocusedLoaded) {
540
+ return;
3493
541
  }
3494
542
  const stored = await chrome.storage.local.get("lastFocused");
3495
543
  state.lastFocused = stored.lastFocused || {};
3496
544
  state.lastFocusedLoaded = true;
3497
545
  }
3498
- async function ensureArchiveWindowIdLoaded() {
3499
- if (state.archiveWindowIdLoaded) {
3500
- return;
3501
- }
3502
- const stored = await chrome.storage.local.get("archiveWindowId");
3503
- state.archiveWindowId = stored.archiveWindowId || null;
3504
- state.archiveWindowIdLoaded = true;
3505
- }
3506
546
  async function setLastFocused(tabId) {
3507
547
  await ensureLastFocusedLoaded();
3508
548
  state.lastFocused[String(tabId)] = Date.now();
@@ -3524,9 +564,9 @@
3524
564
  if (windowId === chrome.windows.WINDOW_ID_NONE) {
3525
565
  return;
3526
566
  }
3527
- chrome.tabs.query({ windowId, active: true }).then((tabs2) => {
3528
- if (tabs2[0] && tabs2[0].id != null) {
3529
- setLastFocused(tabs2[0].id).catch((error) => log("Failed to set last focused", error));
567
+ chrome.tabs.query({ windowId, active: true }).then((tabs) => {
568
+ if (tabs[0] && tabs[0].id != null) {
569
+ setLastFocused(tabs[0].id).catch((error) => log("Failed to set last focused", error));
3530
570
  }
3531
571
  }).catch((error) => log("Failed to query active tab", error));
3532
572
  });
@@ -3545,13 +585,11 @@
3545
585
  sendResponse(id, false, error);
3546
586
  }
3547
587
  }
3548
- function sendProgress(id, payload) {
3549
- if (!state.port) {
3550
- return;
3551
- }
3552
- state.port.postMessage({ id, progress: true, data: payload });
3553
- }
3554
588
  async function handleAction(action, params, requestId) {
589
+ if (action.startsWith("p:")) {
590
+ delete params.client;
591
+ delete params.txid;
592
+ }
3555
593
  switch (action) {
3556
594
  case "ping":
3557
595
  return {
@@ -3563,109 +601,89 @@
3563
601
  dirty: VERSION_INFO.dirty,
3564
602
  component: "extension"
3565
603
  };
3566
- case "version":
3567
- return {
3568
- runtimeId: chrome.runtime.id,
3569
- version: VERSION_INFO.version,
3570
- baseVersion: VERSION_INFO.baseVersion,
3571
- gitSha: VERSION_INFO.gitSha,
3572
- dirty: VERSION_INFO.dirty,
3573
- component: "extension"
3574
- };
3575
- case "list":
3576
- return shapeListSnapshot(await getTabSnapshot());
3577
- case "analyze":
3578
- return await inspect.analyzeTabs(params, requestId, deps);
3579
- case "inspect":
3580
- return await inspect.inspectTabs(params, requestId, deps);
3581
- case "focus":
3582
- return await tabs.focusTab(params);
3583
- case "refresh":
3584
- return await tabs.refreshTabs(params);
3585
- case "open":
3586
- return await tabs.openTabs(params, deps);
3587
- case "group-list":
3588
- return await listGroups(params);
3589
- case "group-update":
3590
- return await groupUpdate(params);
3591
- case "group-ungroup":
3592
- return await groupUngroup(params);
3593
- case "group-assign":
3594
- return await groupAssign(params);
3595
- case "group-gather":
3596
- return await groupGather(params);
3597
- case "move-tab":
3598
- return await move.moveTab(params, deps);
3599
- case "move-group":
3600
- return await move.moveGroup(params, deps);
3601
- case "merge-window":
3602
- return await archive.mergeWindow(params, deps);
3603
- case "archive":
3604
- return await archive.archiveTabs(params, deps);
3605
- case "close":
3606
- return await archive.closeTabs(params, deps);
3607
- case "report":
3608
- return await reportTabs(params);
3609
- case "screenshot":
3610
- return await screenshot.screenshotTabs(params, requestId, deps);
3611
- case "undo":
3612
- return await undoHandlers.undoTransaction(params, deps);
3613
604
  case "reload":
3614
605
  setTimeout(() => chrome.runtime.reload(), 100);
3615
606
  return { reloading: true };
607
+ // --- Primitives: thin Chrome API wrappers (p: prefix) ---
608
+ case "p:snapshot":
609
+ return await getTabSnapshot();
610
+ case "p:tab-get":
611
+ return await chrome.tabs.get(requireFiniteId(params.tabId, "tabId"));
612
+ case "p:tab-query":
613
+ return await chrome.tabs.query(params.query);
614
+ case "p:tab-create":
615
+ return await chrome.tabs.create(params);
616
+ case "p:tab-update": {
617
+ const { tabId: rawTabId, ...updateProps } = params;
618
+ return await chrome.tabs.update(requireFiniteId(rawTabId, "tabId"), updateProps);
619
+ }
620
+ case "p:tab-move": {
621
+ const { tabIds: moveTabIds, ...moveProps } = params;
622
+ return await chrome.tabs.move(moveTabIds, moveProps);
623
+ }
624
+ case "p:tab-remove":
625
+ await chrome.tabs.remove(params.tabIds);
626
+ return { removed: true };
627
+ case "p:tab-reload":
628
+ await chrome.tabs.reload(requireFiniteId(params.tabId, "tabId"));
629
+ return { reloaded: true };
630
+ case "p:tab-group":
631
+ return { groupId: await chrome.tabs.group(params) };
632
+ case "p:tab-ungroup":
633
+ await chrome.tabs.ungroup(params.tabIds);
634
+ return { ungrouped: true };
635
+ case "p:group-update": {
636
+ const { groupId: rawGroupId, ...groupProps } = params;
637
+ return await chrome.tabGroups.update(requireFiniteId(rawGroupId, "groupId"), groupProps);
638
+ }
639
+ case "p:window-create":
640
+ return await chrome.windows.create(params);
641
+ case "p:window-remove":
642
+ await chrome.windows.remove(requireFiniteId(params.windowId, "windowId"));
643
+ return { removed: true };
644
+ case "p:window-update": {
645
+ const { windowId: rawWinId, ...winProps } = params;
646
+ return await chrome.windows.update(requireFiniteId(rawWinId, "windowId"), winProps);
647
+ }
648
+ case "p:execute-script": {
649
+ const targetTabId = requireFiniteId(params.tabId, "tabId");
650
+ const funcName = params.func;
651
+ const funcArgs = params.args || [];
652
+ const timeoutMs = Number(params.timeoutMs) || 8e3;
653
+ switch (funcName) {
654
+ case "extractPageMeta":
655
+ return await content.extractPageMeta(targetTabId, timeoutMs, funcArgs[0] || DESCRIPTION_MAX_LENGTH);
656
+ case "extractSelectorSignal": {
657
+ const specs = funcArgs[0];
658
+ if (!Array.isArray(specs))
659
+ throw new Error("extractSelectorSignal requires specs array as first arg");
660
+ const selectorMaxLen = funcArgs[1] || 500;
661
+ return await content.extractSelectorSignal(targetTabId, specs, timeoutMs, selectorMaxLen);
662
+ }
663
+ default:
664
+ throw new Error(`Unknown execute-script func: ${funcName}`);
665
+ }
666
+ }
667
+ case "p:screenshot-tile": {
668
+ const opts = params.options ?? {};
669
+ const mode = opts.mode === "viewport" || opts.mode === "full" ? opts.mode : "viewport";
670
+ const fmt = opts.format === "png" || opts.format === "jpeg" ? opts.format : "png";
671
+ const quality = typeof opts.quality === "number" ? Math.max(1, Math.min(100, opts.quality)) : 80;
672
+ const tileMaxDim = typeof opts.tileMaxDim === "number" ? Math.max(50, opts.tileMaxDim) : 50;
673
+ const maxBytes = typeof opts.maxBytes === "number" ? Math.max(5e4, opts.maxBytes) : 5e4;
674
+ return await screenshot.captureTabTiles(params.tab, { mode, format: fmt, quality, tileMaxDim, maxBytes }, { delay, executeWithTimeout });
675
+ }
3616
676
  default:
3617
677
  throw new Error(`Unknown action: ${action}`);
3618
678
  }
3619
679
  }
3620
- function shapeListSnapshot(snapshot) {
3621
- return {
3622
- windows: snapshot.windows.map((win) => ({
3623
- windowId: win.windowId,
3624
- focused: win.focused,
3625
- tabs: (win.tabs || []).map((tab) => ({
3626
- tabId: tab.tabId,
3627
- windowId: tab.windowId,
3628
- url: tab.url,
3629
- title: tab.title,
3630
- active: tab.active,
3631
- groupId: tab.groupId,
3632
- groupTitle: tab.groupTitle
3633
- })),
3634
- groups: (win.groups || []).map((group) => ({
3635
- groupId: group.groupId,
3636
- title: group.title,
3637
- color: group.color,
3638
- collapsed: group.collapsed
3639
- }))
3640
- }))
3641
- };
3642
- }
3643
- function resolveWindowIdFromParams(snapshot, value) {
3644
- if (typeof value === "number" && Number.isFinite(value)) {
3645
- return value;
3646
- }
3647
- if (typeof value === "string") {
3648
- const normalized = value.trim().toLowerCase();
3649
- if (normalized === "active") {
3650
- const focused = snapshot.windows.find((win) => win.focused);
3651
- return focused ? focused.windowId : null;
3652
- }
3653
- if (normalized === "last-focused") {
3654
- return getMostRecentFocusedWindowId(snapshot.windows);
3655
- }
3656
- const parsed3 = Number(normalized);
3657
- return Number.isFinite(parsed3) ? parsed3 : null;
3658
- }
3659
- const parsed2 = Number(value);
3660
- return Number.isFinite(parsed2) ? parsed2 : null;
3661
- }
3662
680
  async function getTabSnapshot() {
3663
681
  await ensureLastFocusedLoaded();
3664
682
  const windows = await chrome.windows.getAll({ populate: true, windowTypes: ["normal"] });
3665
- const groups2 = await chrome.tabGroups.query({});
3666
- const groupById = new Map(groups2.map((group) => [group.id, group]));
683
+ const groups = await chrome.tabGroups.query({});
684
+ const groupById = new Map(groups.map((group) => [group.id, group]));
3667
685
  const snapshot = windows.map((win) => {
3668
- const tabs2 = (win.tabs || []).map((tab) => {
686
+ const tabs = (win.tabs || []).map((tab) => {
3669
687
  const group = tab.groupId !== -1 ? groupById.get(tab.groupId) : null;
3670
688
  return {
3671
689
  tabId: tab.id,
@@ -3682,7 +700,7 @@
3682
700
  lastFocusedAt: state.lastFocused[String(tab.id)] || null
3683
701
  };
3684
702
  });
3685
- const windowGroups = groups2.filter((group) => group.windowId === win.id).map((group) => ({
703
+ const windowGroups = groups.filter((group) => group.windowId === win.id).map((group) => ({
3686
704
  groupId: group.id,
3687
705
  title: group.title,
3688
706
  color: group.color,
@@ -3692,7 +710,7 @@
3692
710
  windowId: win.id,
3693
711
  focused: win.focused,
3694
712
  state: win.state,
3695
- tabs: tabs2,
713
+ tabs,
3696
714
  groups: windowGroups
3697
715
  };
3698
716
  });
@@ -3701,172 +719,5 @@
3701
719
  windows: snapshot
3702
720
  };
3703
721
  }
3704
- function flattenTabs(snapshot) {
3705
- const result = [];
3706
- for (const win of snapshot.windows) {
3707
- for (const tab of win.tabs) {
3708
- result.push(tab);
3709
- }
3710
- }
3711
- return result;
3712
- }
3713
- function resolveGroupByTitle(snapshot, groupTitle, windowId) {
3714
- return groups.resolveGroupByTitle(snapshot, buildWindowLabels, groupTitle, windowId);
3715
- }
3716
- function resolveGroupById(snapshot, groupId) {
3717
- return groups.resolveGroupById(snapshot, buildWindowLabels, groupId);
3718
- }
3719
- function buildWindowLabels(snapshot) {
3720
- const labels = /* @__PURE__ */ new Map();
3721
- snapshot.windows.forEach((win, index) => {
3722
- labels.set(win.windowId, `W${index + 1}`);
3723
- });
3724
- return labels;
3725
- }
3726
- function selectTabsByScope(snapshot, params) {
3727
- const allTabs = flattenTabs(snapshot);
3728
- if (params.tabIds && params.tabIds.length) {
3729
- const idSet = new Set(params.tabIds.map(Number));
3730
- return { tabs: allTabs.filter((tab) => idSet.has(tab.tabId)) };
3731
- }
3732
- if (params.groupId) {
3733
- const groupId = Number(params.groupId);
3734
- return { tabs: allTabs.filter((tab) => tab.groupId === groupId) };
3735
- }
3736
- if (params.groupTitle) {
3737
- const windowId = params.windowId != null ? resolveWindowIdFromParams(snapshot, params.windowId) ?? void 0 : void 0;
3738
- const resolved = resolveGroupByTitle(snapshot, params.groupTitle, windowId);
3739
- if (resolved.error) {
3740
- return { tabs: [], error: resolved.error };
3741
- }
3742
- const match = resolved.match;
3743
- return {
3744
- tabs: allTabs.filter((tab) => tab.groupId === match.group.groupId && tab.windowId === match.windowId)
3745
- };
3746
- }
3747
- if (params.windowId) {
3748
- const windowId = resolveWindowIdFromParams(snapshot, params.windowId);
3749
- if (!Number.isFinite(windowId)) {
3750
- return { tabs: [] };
3751
- }
3752
- return { tabs: allTabs.filter((tab) => tab.windowId === windowId) };
3753
- }
3754
- if (params.all) {
3755
- return { tabs: allTabs };
3756
- }
3757
- const focusedWindow = snapshot.windows.find((win) => win.focused);
3758
- if (!focusedWindow) {
3759
- return { tabs: [] };
3760
- }
3761
- return { tabs: focusedWindow.tabs };
3762
- }
3763
- var deps = {
3764
- getTabSnapshot,
3765
- selectTabsByScope,
3766
- sendProgress,
3767
- log,
3768
- resolveWindowIdFromParams,
3769
- resolveGroupByTitle,
3770
- resolveGroupById,
3771
- buildWindowLabels,
3772
- getArchiveWindowId,
3773
- setArchiveWindowId,
3774
- delay,
3775
- executeWithTimeout,
3776
- isScriptableUrl,
3777
- waitForTabReady
3778
- };
3779
- async function listGroups(params) {
3780
- return groups.listGroups(params, deps);
3781
- }
3782
- async function groupUpdate(params) {
3783
- return groups.groupUpdate(params, deps);
3784
- }
3785
- async function groupUngroup(params) {
3786
- return groups.groupUngroup(params, deps);
3787
- }
3788
- async function groupAssign(params) {
3789
- return groups.groupAssign(params, deps);
3790
- }
3791
- async function groupGather(params) {
3792
- return groups.groupGather(params, deps);
3793
- }
3794
- async function getArchiveWindowId() {
3795
- await ensureArchiveWindowIdLoaded();
3796
- return state.archiveWindowId;
3797
- }
3798
- async function setArchiveWindowId(id) {
3799
- state.archiveWindowId = id;
3800
- state.archiveWindowIdLoaded = true;
3801
- await chrome.storage.local.set({ archiveWindowId: id });
3802
- }
3803
- async function extractDescription(tabId) {
3804
- try {
3805
- const [{ result }] = await chrome.scripting.executeScript({
3806
- target: { tabId },
3807
- func: () => {
3808
- const pickContent = (selector) => {
3809
- const el = document.querySelector(selector);
3810
- if (!el) {
3811
- return "";
3812
- }
3813
- const content2 = el.getAttribute("content") || el.textContent || "";
3814
- return content2.trim();
3815
- };
3816
- let description = pickContent("meta[name='description']") || pickContent("meta[property='og:description']") || pickContent("meta[name='twitter:description']");
3817
- if (!description) {
3818
- const h1 = document.querySelector("h1");
3819
- const h1Text = h1 ? h1.textContent?.trim() : "";
3820
- const paragraphs = Array.from(document.querySelectorAll("p")).map((p) => p.textContent?.replace(/\s+/g, " ").trim() || "").filter((text) => text.length > 40);
3821
- const pText = paragraphs.length ? paragraphs[0] : "";
3822
- if (h1Text && pText) {
3823
- description = `${h1Text} - ${pText}`;
3824
- } else {
3825
- description = h1Text || pText;
3826
- }
3827
- }
3828
- return description.replace(/\s+/g, " ").trim();
3829
- }
3830
- });
3831
- if (!result) {
3832
- return "";
3833
- }
3834
- return String(result).slice(0, DESCRIPTION_MAX_LENGTH);
3835
- } catch {
3836
- return "";
3837
- }
3838
- }
3839
- async function reportTabs(params) {
3840
- const snapshot = await getTabSnapshot();
3841
- const selection = selectTabsByScope(snapshot, params);
3842
- if (selection.error) {
3843
- throw selection.error;
3844
- }
3845
- const windowLabels = buildWindowLabels(snapshot);
3846
- const tabs2 = selection.tabs;
3847
- const entries = [];
3848
- for (const tab of tabs2) {
3849
- const description = isScriptableUrl(tab.url) ? await extractDescription(tab.tabId) : "";
3850
- entries.push({
3851
- tabId: tab.tabId,
3852
- windowId: tab.windowId,
3853
- windowLabel: windowLabels.get(tab.windowId) || `W${tab.windowId}`,
3854
- groupId: tab.groupId,
3855
- groupTitle: tab.groupTitle,
3856
- groupColor: tab.groupColor,
3857
- url: tab.url,
3858
- title: tab.title,
3859
- description,
3860
- lastFocusedAt: tab.lastFocusedAt
3861
- });
3862
- }
3863
- return {
3864
- generatedAt: Date.now(),
3865
- entries,
3866
- totals: {
3867
- tabs: entries.length
3868
- }
3869
- };
3870
- }
3871
722
  self.__tabctl = { state, connectNative };
3872
723
  })();