tabctl 0.3.1 → 0.4.0

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.
@@ -11,10 +11,15 @@ exports.listGroups = listGroups;
11
11
  exports.groupUpdate = groupUpdate;
12
12
  exports.groupUngroup = groupUngroup;
13
13
  exports.groupAssign = groupAssign;
14
+ exports.groupGather = groupGather;
14
15
  function getGroupTabs(windowSnapshot, groupId) {
15
16
  return windowSnapshot.tabs
16
17
  .filter((tab) => tab.groupId === groupId)
17
- .sort((a, b) => (Number(a.index) || 0) - (Number(b.index) || 0));
18
+ .sort((a, b) => {
19
+ const ai = Number(a.index);
20
+ const bi = Number(b.index);
21
+ return (Number.isFinite(ai) ? ai : 0) - (Number.isFinite(bi) ? bi : 0);
22
+ });
18
23
  }
19
24
  function listGroupSummaries(snapshot, buildWindowLabels, windowId) {
20
25
  const windowLabels = buildWindowLabels(snapshot);
@@ -83,8 +88,8 @@ function resolveGroupByTitle(snapshot, buildWindowLabels, groupTitle, windowId)
83
88
  if (matches.length > 1) {
84
89
  return {
85
90
  error: {
86
- message: "Group title is ambiguous. Provide a windowId.",
87
- hint: "Use --window to disambiguate group titles.",
91
+ 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.`,
92
+ hint: "Use group-gather to merge duplicates, --group-id to target by ID, or --window to narrow scope.",
88
93
  matches: matches.map((match) => summarizeGroupMatch(match, windowLabels)),
89
94
  availableGroups,
90
95
  },
@@ -441,3 +446,84 @@ async function groupAssign(params, deps) {
441
446
  txid: params.txid || null,
442
447
  };
443
448
  }
449
+ async function groupGather(params, deps) {
450
+ const snapshot = await deps.getTabSnapshot();
451
+ const windows = snapshot.windows;
452
+ const windowIdParam = params.windowId != null ? deps.resolveWindowIdFromParams(snapshot, params.windowId) : null;
453
+ if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
454
+ throw new Error("Window not found");
455
+ }
456
+ const groupTitleFilter = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
457
+ const merged = [];
458
+ const undoEntries = [];
459
+ for (const win of windows) {
460
+ if (windowIdParam && win.windowId !== windowIdParam)
461
+ continue;
462
+ const byTitle = new Map();
463
+ for (const group of win.groups) {
464
+ const title = typeof group.title === "string" ? group.title : "";
465
+ if (!title)
466
+ continue;
467
+ if (groupTitleFilter && title !== groupTitleFilter)
468
+ continue;
469
+ if (!byTitle.has(title))
470
+ byTitle.set(title, []);
471
+ byTitle.get(title).push(group);
472
+ }
473
+ for (const [title, titleGroups] of byTitle) {
474
+ if (titleGroups.length < 2)
475
+ continue;
476
+ const groupsWithIndex = titleGroups.map((g) => {
477
+ const tabs = win.tabs.filter((t) => t.groupId === g.groupId);
478
+ const minIndex = Math.min(...tabs.map((t) => {
479
+ const idx = Number(t.index);
480
+ return Number.isFinite(idx) ? idx : Infinity;
481
+ }));
482
+ return { group: g, tabs, minIndex };
483
+ });
484
+ groupsWithIndex.sort((a, b) => a.minIndex - b.minIndex);
485
+ const primary = groupsWithIndex[0];
486
+ const duplicates = groupsWithIndex.slice(1);
487
+ let movedTabs = 0;
488
+ for (const dup of duplicates) {
489
+ const tabIds = dup.tabs
490
+ .map((t) => t.tabId)
491
+ .filter((id) => typeof id === "number");
492
+ if (tabIds.length > 0) {
493
+ for (const tab of dup.tabs) {
494
+ undoEntries.push({
495
+ tabId: tab.tabId,
496
+ windowId: win.windowId,
497
+ index: tab.index,
498
+ groupId: tab.groupId,
499
+ groupTitle: tab.groupTitle,
500
+ groupColor: tab.groupColor,
501
+ groupCollapsed: dup.group.collapsed ?? null,
502
+ });
503
+ }
504
+ await chrome.tabs.group({ groupId: primary.group.groupId, tabIds });
505
+ movedTabs += tabIds.length;
506
+ }
507
+ }
508
+ merged.push({
509
+ windowId: win.windowId,
510
+ groupTitle: title,
511
+ primaryGroupId: primary.group.groupId,
512
+ mergedGroupCount: duplicates.length,
513
+ movedTabs,
514
+ });
515
+ }
516
+ }
517
+ return {
518
+ merged,
519
+ summary: {
520
+ mergedGroups: merged.reduce((sum, m) => sum + m.mergedGroupCount, 0),
521
+ movedTabs: merged.reduce((sum, m) => sum + m.movedTabs, 0),
522
+ },
523
+ undo: {
524
+ action: "group-gather",
525
+ tabs: undoEntries,
526
+ },
527
+ txid: params.txid || null,
528
+ };
529
+ }
@@ -15,7 +15,7 @@ exports.scrollToPosition = scrollToPosition;
15
15
  exports.captureTabTiles = captureTabTiles;
16
16
  exports.screenshotTabs = screenshotTabs;
17
17
  exports.SCREENSHOT_TILE_MAX_DIM = 2000;
18
- exports.SCREENSHOT_MAX_BYTES = 2000000;
18
+ exports.SCREENSHOT_MAX_BYTES = 2_000_000;
19
19
  exports.SCREENSHOT_QUALITY = 80;
20
20
  exports.SCREENSHOT_SCROLL_DELAY_MS = 150;
21
21
  exports.SCREENSHOT_CAPTURE_DELAY_MS = 350;
@@ -288,7 +288,7 @@ async function screenshotTabs(params, requestId, deps) {
288
288
  const adjustedTileMaxDim = tileMaxDim < 50 ? 50 : tileMaxDim;
289
289
  const maxBytesRaw = Number(params.maxBytes);
290
290
  const maxBytes = Number.isFinite(maxBytesRaw) && maxBytesRaw > 0 ? Math.floor(maxBytesRaw) : exports.SCREENSHOT_MAX_BYTES;
291
- const adjustedMaxBytes = maxBytes < 50000 ? 50000 : maxBytes;
291
+ const adjustedMaxBytes = maxBytes < 50_000 ? 50_000 : maxBytes;
292
292
  const progressEnabled = params.progress === true;
293
293
  const tabs = selection.tabs;
294
294
  const entries = [];
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  // Tab operations — extracted from background.ts (pure structural refactor).
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  exports.getMostRecentFocusedWindowId = getMostRecentFocusedWindowId;
5
8
  exports.normalizeUrl = normalizeUrl;
@@ -8,6 +11,7 @@ exports.resolveOpenWindow = resolveOpenWindow;
8
11
  exports.focusTab = focusTab;
9
12
  exports.refreshTabs = refreshTabs;
10
13
  exports.openTabs = openTabs;
14
+ const normalize_url_1 = __importDefault(require("normalize-url"));
11
15
  function getMostRecentFocusedWindowId(windows) {
12
16
  let bestWindowId = null;
13
17
  let bestFocusedAt = -Infinity;
@@ -29,42 +33,26 @@ function normalizeUrl(rawUrl) {
29
33
  if (!rawUrl || typeof rawUrl !== "string") {
30
34
  return null;
31
35
  }
32
- let url;
33
36
  try {
34
- url = new URL(rawUrl);
37
+ return (0, normalize_url_1.default)(rawUrl, {
38
+ stripHash: true,
39
+ removeQueryParameters: [
40
+ /^utm_\w+$/i,
41
+ "fbclid",
42
+ "gclid",
43
+ "igshid",
44
+ "mc_cid",
45
+ "mc_eid",
46
+ "ref",
47
+ "ref_src",
48
+ "ref_url",
49
+ "si",
50
+ ],
51
+ });
35
52
  }
36
53
  catch {
37
54
  return null;
38
55
  }
39
- if (url.protocol !== "http:" && url.protocol !== "https:") {
40
- return null;
41
- }
42
- url.hash = "";
43
- const dropKeys = new Set([
44
- "fbclid",
45
- "gclid",
46
- "igshid",
47
- "mc_cid",
48
- "mc_eid",
49
- "ref",
50
- "ref_src",
51
- "ref_url",
52
- "utm_campaign",
53
- "utm_content",
54
- "utm_medium",
55
- "utm_source",
56
- "utm_term",
57
- "utm_name",
58
- "si",
59
- ]);
60
- for (const key of Array.from(url.searchParams.keys())) {
61
- if (key.startsWith("utm_") || dropKeys.has(key)) {
62
- url.searchParams.delete(key);
63
- }
64
- }
65
- const search = url.searchParams.toString();
66
- url.search = search ? `?${search}` : "";
67
- return url.toString();
68
56
  }
69
57
  function normalizeTabIndex(value) {
70
58
  const index = Number(value);
@@ -200,6 +188,8 @@ async function openTabs(params, deps) {
200
188
  throw new Error("Only one target position is allowed");
201
189
  }
202
190
  const newWindow = params.newWindow === true;
191
+ const forceNewGroup = params.newGroup === true;
192
+ const allowDuplicates = params.allowDuplicates === true;
203
193
  if (!urls.length && !newWindow) {
204
194
  throw new Error("No URLs provided");
205
195
  }
@@ -287,6 +277,13 @@ async function openTabs(params, deps) {
287
277
  }
288
278
  const snapshot = await deps.getTabSnapshot();
289
279
  let openParams = params;
280
+ // Auto-resolve window by group name when no explicit window selector is provided
281
+ if (groupTitle && !forceNewGroup && openParams.windowId == null && !openParams.windowGroupTitle && !openParams.windowTabId && !openParams.windowUrl) {
282
+ const groupWindows = snapshot.windows.filter((win) => win.groups.some((g) => g.title === groupTitle));
283
+ if (groupWindows.length === 1) {
284
+ openParams = { ...openParams, windowId: groupWindows[0].windowId };
285
+ }
286
+ }
290
287
  if (params.windowId == null && (beforeTabId != null || afterTabId != null)) {
291
288
  const anchorId = beforeTabId != null ? beforeTabId : afterTabId;
292
289
  const anchorWindow = snapshot.windows
@@ -304,6 +301,25 @@ async function openTabs(params, deps) {
304
301
  if (!windowSnapshot) {
305
302
  throw new Error("Window snapshot unavailable");
306
303
  }
304
+ // Resolve existing group for reuse
305
+ let existingGroupId = null;
306
+ const existingUrlSet = new Set();
307
+ if (groupTitle && !forceNewGroup) {
308
+ const matchingGroups = windowSnapshot.groups.filter((g) => g.title === groupTitle);
309
+ if (matchingGroups.length > 1) {
310
+ 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.`);
311
+ }
312
+ if (matchingGroups.length === 1) {
313
+ existingGroupId = matchingGroups[0].groupId;
314
+ const existingTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === existingGroupId);
315
+ for (const tab of existingTabs) {
316
+ const norm = normalizeUrl(tab.url);
317
+ if (norm) {
318
+ existingUrlSet.add(norm);
319
+ }
320
+ }
321
+ }
322
+ }
307
323
  const created = [];
308
324
  const skipped = [];
309
325
  let insertIndex = null;
@@ -339,8 +355,25 @@ async function openTabs(params, deps) {
339
355
  }
340
356
  insertIndex = beforeTabId != null ? anchorIndex : anchorIndex + 1;
341
357
  }
358
+ // Default insert position: append after existing group tabs
359
+ if (existingGroupId != null && insertIndex == null && beforeTabId == null && afterTabId == null) {
360
+ const groupTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === existingGroupId);
361
+ const indices = groupTabs
362
+ .map((tab) => normalizeTabIndex(tab.index))
363
+ .filter((value) => value != null);
364
+ if (indices.length) {
365
+ insertIndex = Math.max(...indices) + 1;
366
+ }
367
+ }
342
368
  let nextIndex = insertIndex;
343
369
  for (const url of urls) {
370
+ if (!allowDuplicates && existingGroupId != null) {
371
+ const norm = normalizeUrl(url);
372
+ if (norm && existingUrlSet.has(norm)) {
373
+ skipped.push({ url, reason: "duplicate" });
374
+ continue;
375
+ }
376
+ }
344
377
  try {
345
378
  const createOptions = { windowId, url, active: false };
346
379
  if (nextIndex != null) {
@@ -365,12 +398,18 @@ async function openTabs(params, deps) {
365
398
  try {
366
399
  const tabIds = created.map((tab) => tab.tabId).filter((id) => typeof id === "number");
367
400
  if (tabIds.length > 0) {
368
- groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId } });
369
- const update = { title: groupTitle };
370
- if (groupColor) {
371
- update.color = groupColor;
401
+ if (existingGroupId != null) {
402
+ // Reuse existing group
403
+ groupId = await chrome.tabs.group({ groupId: existingGroupId, tabIds });
404
+ }
405
+ else {
406
+ groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId } });
407
+ const update = { title: groupTitle };
408
+ if (groupColor) {
409
+ update.color = groupColor;
410
+ }
411
+ await chrome.tabGroups.update(groupId, update);
372
412
  }
373
- await chrome.tabGroups.update(groupId, update);
374
413
  }
375
414
  }
376
415
  catch (error) {
@@ -378,6 +417,28 @@ async function openTabs(params, deps) {
378
417
  groupId = null;
379
418
  }
380
419
  }
420
+ // All-dupes case: report existing group even when no new tabs were created
421
+ if (groupTitle && created.length === 0 && existingGroupId != null) {
422
+ groupId = existingGroupId;
423
+ }
424
+ // Reorder: groups before ungrouped tabs
425
+ try {
426
+ const freshTabs = await chrome.tabs.query({ windowId });
427
+ freshTabs.sort((a, b) => a.index - b.index);
428
+ const firstUngroupedIndex = freshTabs.findIndex(t => (t.groupId ?? -1) === -1);
429
+ if (firstUngroupedIndex >= 0) {
430
+ const groupedAfterUngrouped = freshTabs.filter((t, i) => i > firstUngroupedIndex && (t.groupId ?? -1) !== -1);
431
+ if (groupedAfterUngrouped.length > 0) {
432
+ const tabIdsToMove = groupedAfterUngrouped.map(t => t.id).filter(id => typeof id === "number");
433
+ if (tabIdsToMove.length > 0) {
434
+ await chrome.tabs.move(tabIdsToMove, { index: firstUngroupedIndex });
435
+ }
436
+ }
437
+ }
438
+ }
439
+ catch (err) {
440
+ deps.log("Failed to reorder groups before ungrouped tabs", err);
441
+ }
381
442
  return {
382
443
  windowId,
383
444
  groupId,
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.undoGroupUpdate = undoGroupUpdate;
5
5
  exports.undoGroupUngroup = undoGroupUngroup;
6
6
  exports.undoGroupAssign = undoGroupAssign;
7
+ exports.undoGroupGather = undoGroupGather;
7
8
  exports.undoMoveTab = undoMoveTab;
8
9
  exports.undoMoveGroup = undoMoveGroup;
9
10
  exports.undoMergeWindow = undoMergeWindow;
@@ -197,6 +198,10 @@ async function undoGroupAssign(undo, deps) {
197
198
  const tabs = undo.tabs || [];
198
199
  return await restoreTabsFromUndo(tabs, deps);
199
200
  }
201
+ async function undoGroupGather(undo, deps) {
202
+ const tabs = undo.tabs || [];
203
+ return await restoreTabsFromUndo(tabs, deps);
204
+ }
200
205
  async function undoMoveTab(undo, deps) {
201
206
  const from = undo.from || {};
202
207
  const entry = {
@@ -426,6 +431,9 @@ async function undoTransaction(params, deps) {
426
431
  if (undo.action === "group-assign") {
427
432
  return await undoGroupAssign(undo, deps);
428
433
  }
434
+ if (undo.action === "group-gather") {
435
+ return await undoGroupGather(undo, deps);
436
+ }
429
437
  if (undo.action === "move-tab") {
430
438
  return await undoMoveTab(undo, deps);
431
439
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Tab Control",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "description": "Archive and manage browser tabs with CLI support",
6
6
  "permissions": [
7
7
  "tabs",
@@ -19,5 +19,5 @@
19
19
  "background": {
20
20
  "service_worker": "background.js"
21
21
  },
22
- "version_name": "0.3.1"
22
+ "version_name": "0.4.0"
23
23
  }
@@ -17,28 +17,28 @@ var require_config = __commonJS({
17
17
  exports2.resetConfig = resetConfig;
18
18
  exports2.expandEnvVars = expandEnvVars;
19
19
  exports2.resolveConfig = resolveConfig;
20
- var os_1 = __importDefault2(require("os"));
21
- var path_1 = __importDefault2(require("path"));
22
- var crypto_12 = __importDefault2(require("crypto"));
23
- var fs_12 = __importDefault2(require("fs"));
20
+ var node_os_1 = __importDefault2(require("node:os"));
21
+ var node_path_1 = __importDefault2(require("node:path"));
22
+ var node_crypto_12 = __importDefault2(require("node:crypto"));
23
+ var node_fs_12 = __importDefault2(require("node:fs"));
24
24
  function defaultConfigBase() {
25
25
  if (process.platform === "win32") {
26
- return process.env.APPDATA || path_1.default.join(os_1.default.homedir(), "AppData", "Roaming");
26
+ return process.env.APPDATA || node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming");
27
27
  }
28
- return path_1.default.join(os_1.default.homedir(), ".config");
28
+ return node_path_1.default.join(node_os_1.default.homedir(), ".config");
29
29
  }
30
30
  function defaultStateBase() {
31
31
  if (process.platform === "win32") {
32
- return process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), "AppData", "Local");
32
+ return process.env.LOCALAPPDATA || node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Local");
33
33
  }
34
- return path_1.default.join(os_1.default.homedir(), ".local", "state");
34
+ return node_path_1.default.join(node_os_1.default.homedir(), ".local", "state");
35
35
  }
36
36
  function resolveSocketPath(dataDir) {
37
37
  if (process.platform === "win32") {
38
- const hash = crypto_12.default.createHash("sha256").update(dataDir).digest("hex").slice(0, 12);
38
+ const hash = node_crypto_12.default.createHash("sha256").update(dataDir).digest("hex").slice(0, 12);
39
39
  return `\\\\.\\pipe\\tabctl-${hash}`;
40
40
  }
41
- return path_1.default.join(dataDir, "tabctl.sock");
41
+ return node_path_1.default.join(dataDir, "tabctl.sock");
42
42
  }
43
43
  var cached;
44
44
  function resetConfig() {
@@ -53,23 +53,23 @@ var require_config = __commonJS({
53
53
  function resolveConfig(profileName) {
54
54
  if (!profileName && cached)
55
55
  return cached;
56
- const configDir = process.env.TABCTL_CONFIG_DIR || path_1.default.join(process.env.XDG_CONFIG_HOME || defaultConfigBase(), "tabctl");
56
+ const configDir = process.env.TABCTL_CONFIG_DIR || node_path_1.default.join(process.env.XDG_CONFIG_HOME || defaultConfigBase(), "tabctl");
57
57
  let fileConfig = {};
58
58
  try {
59
- const raw = fs_12.default.readFileSync(path_1.default.join(configDir, "config.json"), "utf-8");
59
+ const raw = node_fs_12.default.readFileSync(node_path_1.default.join(configDir, "config.json"), "utf-8");
60
60
  fileConfig = JSON.parse(raw);
61
61
  } catch {
62
62
  }
63
63
  let dataDir;
64
64
  if (typeof fileConfig.dataDir === "string" && fileConfig.dataDir) {
65
65
  dataDir = expandEnvVars(fileConfig.dataDir);
66
- if (!path_1.default.isAbsolute(dataDir)) {
66
+ if (!node_path_1.default.isAbsolute(dataDir)) {
67
67
  throw new Error(`dataDir in config.json must be an absolute path (got: ${dataDir}). Use $HOME or full paths.`);
68
68
  }
69
69
  } else if (process.env.TABCTL_CONFIG_DIR) {
70
- dataDir = path_1.default.join(configDir, "data");
70
+ dataDir = node_path_1.default.join(configDir, "data");
71
71
  } else {
72
- dataDir = path_1.default.join(process.env.XDG_STATE_HOME || defaultStateBase(), "tabctl");
72
+ dataDir = node_path_1.default.join(process.env.XDG_STATE_HOME || defaultStateBase(), "tabctl");
73
73
  }
74
74
  const baseDataDir = dataDir;
75
75
  const explicitProfile = profileName;
@@ -77,7 +77,7 @@ var require_config = __commonJS({
77
77
  let activeProfileName;
78
78
  if (effectiveProfile) {
79
79
  try {
80
- const raw = fs_12.default.readFileSync(path_1.default.join(configDir, "profiles.json"), "utf-8");
80
+ const raw = node_fs_12.default.readFileSync(node_path_1.default.join(configDir, "profiles.json"), "utf-8");
81
81
  const registry = JSON.parse(raw);
82
82
  const profile = registry.profiles[effectiveProfile];
83
83
  if (profile) {
@@ -96,7 +96,7 @@ var require_config = __commonJS({
96
96
  }
97
97
  } else {
98
98
  try {
99
- const raw = fs_12.default.readFileSync(path_1.default.join(configDir, "profiles.json"), "utf-8");
99
+ const raw = node_fs_12.default.readFileSync(node_path_1.default.join(configDir, "profiles.json"), "utf-8");
100
100
  const registry = JSON.parse(raw);
101
101
  if (registry.default && registry.profiles[registry.default]) {
102
102
  dataDir = registry.profiles[registry.default].dataDir;
@@ -110,9 +110,9 @@ var require_config = __commonJS({
110
110
  dataDir,
111
111
  baseDataDir,
112
112
  socketPath: process.env.TABCTL_SOCKET || resolveSocketPath(dataDir),
113
- undoLog: path_1.default.join(dataDir, "undo.jsonl"),
113
+ undoLog: node_path_1.default.join(dataDir, "undo.jsonl"),
114
114
  wrapperDir: dataDir,
115
- policyPath: path_1.default.join(configDir, "policy.json"),
115
+ policyPath: node_path_1.default.join(configDir, "policy.json"),
116
116
  activeProfileName
117
117
  };
118
118
  if (!profileName) {
@@ -129,8 +129,8 @@ var require_version = __commonJS({
129
129
  "use strict";
130
130
  Object.defineProperty(exports2, "__esModule", { value: true });
131
131
  exports2.DIRTY = exports2.GIT_SHA = exports2.VERSION = exports2.BASE_VERSION = void 0;
132
- exports2.BASE_VERSION = "0.3.1";
133
- exports2.VERSION = "0.3.1";
132
+ exports2.BASE_VERSION = "0.4.0";
133
+ exports2.VERSION = "0.4.0";
134
134
  exports2.GIT_SHA = null;
135
135
  exports2.DIRTY = false;
136
136
  }
@@ -149,18 +149,18 @@ var require_undo = __commonJS({
149
149
  exports2.filterByRetention = filterByRetention;
150
150
  exports2.findUndoRecord = findUndoRecord;
151
151
  exports2.findLatestUndoRecord = findLatestUndoRecord;
152
- var fs_12 = __importDefault2(require("fs"));
153
- var path_1 = __importDefault2(require("path"));
152
+ var node_fs_12 = __importDefault2(require("node:fs"));
153
+ var node_path_1 = __importDefault2(require("node:path"));
154
154
  var DEFAULT_RETENTION_DAYS = 30;
155
155
  function appendUndoRecord(filePath, record) {
156
- const dir = path_1.default.dirname(filePath);
157
- fs_12.default.mkdirSync(dir, { recursive: true, mode: 448 });
158
- fs_12.default.appendFileSync(filePath, `${JSON.stringify(record)}
156
+ const dir = node_path_1.default.dirname(filePath);
157
+ node_fs_12.default.mkdirSync(dir, { recursive: true, mode: 448 });
158
+ node_fs_12.default.appendFileSync(filePath, `${JSON.stringify(record)}
159
159
  `, "utf8");
160
160
  }
161
161
  function readUndoRecords(filePath) {
162
162
  try {
163
- const content = fs_12.default.readFileSync(filePath, "utf8");
163
+ const content = node_fs_12.default.readFileSync(filePath, "utf8");
164
164
  const lines = content.split("\n").filter(Boolean);
165
165
  const records = [];
166
166
  for (const line of lines) {
@@ -205,6 +205,7 @@ var require_handlers = __commonJS({
205
205
  "dist/host/lib/handlers.js"(exports2) {
206
206
  "use strict";
207
207
  Object.defineProperty(exports2, "__esModule", { value: true });
208
+ exports2.LOCAL_ACTIONS = exports2.UNDO_ACTIONS = void 0;
208
209
  exports2.respond = respond;
209
210
  exports2.refreshTimeout = refreshTimeout;
210
211
  exports2.forwardToExtension = forwardToExtension;
@@ -216,17 +217,18 @@ var require_handlers = __commonJS({
216
217
  var MAX_RESPONSE_BYTES = 20 * 1024 * 1024;
217
218
  var HISTORY_LIMIT_DEFAULT = 20;
218
219
  var RETENTION_DAYS = 30;
219
- var UNDO_ACTIONS = /* @__PURE__ */ new Set([
220
+ exports2.UNDO_ACTIONS = /* @__PURE__ */ new Set([
220
221
  "archive",
221
222
  "close",
222
223
  "group-update",
223
224
  "group-ungroup",
224
225
  "group-assign",
226
+ "group-gather",
225
227
  "move-tab",
226
228
  "move-group",
227
229
  "merge-window"
228
230
  ]);
229
- var LOCAL_ACTIONS = /* @__PURE__ */ new Set(["history", "undo", "version"]);
231
+ exports2.LOCAL_ACTIONS = /* @__PURE__ */ new Set(["history", "undo", "version"]);
230
232
  function respond(socket, payload) {
231
233
  const serialized = JSON.stringify(payload);
232
234
  if (Buffer.byteLength(serialized, "utf8") > MAX_RESPONSE_BYTES) {
@@ -265,7 +267,7 @@ var require_handlers = __commonJS({
265
267
  if (txid) {
266
268
  params.txid = txid;
267
269
  }
268
- if (!LOCAL_ACTIONS.has(request.action)) {
270
+ if (!exports2.LOCAL_ACTIONS.has(request.action)) {
269
271
  params.client = {
270
272
  component: "host",
271
273
  version: version_1.VERSION
@@ -362,7 +364,7 @@ var require_handlers = __commonJS({
362
364
  });
363
365
  return;
364
366
  }
365
- if (UNDO_ACTIONS.has(pendingRequest.action)) {
367
+ if (exports2.UNDO_ACTIONS.has(pendingRequest.action)) {
366
368
  const record = {
367
369
  txid: pendingRequest.txid,
368
370
  createdAt: Date.now(),
@@ -518,7 +520,7 @@ var require_handlers = __commonJS({
518
520
  }, { txid });
519
521
  return;
520
522
  }
521
- if (UNDO_ACTIONS.has(action)) {
523
+ if (exports2.UNDO_ACTIONS.has(action)) {
522
524
  const txid = deps2.createId("tx");
523
525
  forwardToExtension(deps2, socket, request, { txid });
524
526
  return;
@@ -533,9 +535,9 @@ var __importDefault = exports && exports.__importDefault || function(mod) {
533
535
  return mod && mod.__esModule ? mod : { "default": mod };
534
536
  };
535
537
  Object.defineProperty(exports, "__esModule", { value: true });
536
- var fs_1 = __importDefault(require("fs"));
537
- var net_1 = __importDefault(require("net"));
538
- var crypto_1 = __importDefault(require("crypto"));
538
+ var node_fs_1 = __importDefault(require("node:fs"));
539
+ var node_net_1 = __importDefault(require("node:net"));
540
+ var node_crypto_1 = __importDefault(require("node:crypto"));
539
541
  var config_1 = require_config();
540
542
  var handlers_1 = require_handlers();
541
543
  var config;
@@ -555,10 +557,10 @@ function log(...args) {
555
557
  `);
556
558
  }
557
559
  function ensureDir() {
558
- fs_1.default.mkdirSync(SOCKET_DIR, { recursive: true, mode: 448 });
560
+ node_fs_1.default.mkdirSync(SOCKET_DIR, { recursive: true, mode: 448 });
559
561
  }
560
562
  function createId(prefix) {
561
- return `${prefix}-${Date.now()}-${crypto_1.default.randomBytes(4).toString("hex")}`;
563
+ return `${prefix}-${Date.now()}-${node_crypto_1.default.randomBytes(4).toString("hex")}`;
562
564
  }
563
565
  function sendNative(message) {
564
566
  const json = JSON.stringify(message);
@@ -601,10 +603,10 @@ process.stdin.on("end", () => {
601
603
  });
602
604
  function startSocketServer() {
603
605
  ensureDir();
604
- if (process.platform !== "win32" && fs_1.default.existsSync(SOCKET_PATH)) {
605
- fs_1.default.unlinkSync(SOCKET_PATH);
606
+ if (process.platform !== "win32" && node_fs_1.default.existsSync(SOCKET_PATH)) {
607
+ node_fs_1.default.unlinkSync(SOCKET_PATH);
606
608
  }
607
- const server2 = net_1.default.createServer((socket) => {
609
+ const server2 = node_net_1.default.createServer((socket) => {
608
610
  socket.setEncoding("utf8");
609
611
  let buffer = "";
610
612
  socket.on("data", (data) => {
@@ -644,7 +646,7 @@ function startSocketServer() {
644
646
  server2.listen(SOCKET_PATH, () => {
645
647
  if (process.platform !== "win32") {
646
648
  try {
647
- fs_1.default.chmodSync(SOCKET_PATH, 384);
649
+ node_fs_1.default.chmodSync(SOCKET_PATH, 384);
648
650
  } catch {
649
651
  }
650
652
  }
@@ -654,8 +656,8 @@ function startSocketServer() {
654
656
  }
655
657
  function cleanupAndExit(code) {
656
658
  try {
657
- if (process.platform !== "win32" && fs_1.default.existsSync(SOCKET_PATH)) {
658
- fs_1.default.unlinkSync(SOCKET_PATH);
659
+ if (process.platform !== "win32" && node_fs_1.default.existsSync(SOCKET_PATH)) {
660
+ node_fs_1.default.unlinkSync(SOCKET_PATH);
659
661
  }
660
662
  } catch {
661
663
  }