tabctl 0.5.3 → 0.6.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +135 -35
  2. package/dist/extension/background.js +179 -3155
  3. package/dist/extension/lib/content.js +0 -115
  4. package/dist/extension/lib/screenshot.js +0 -93
  5. package/dist/extension/manifest.json +2 -2
  6. package/package.json +13 -5
  7. package/dist/cli/lib/args.js +0 -141
  8. package/dist/cli/lib/client.js +0 -83
  9. package/dist/cli/lib/commands/doctor.js +0 -134
  10. package/dist/cli/lib/commands/index.js +0 -51
  11. package/dist/cli/lib/commands/list.js +0 -159
  12. package/dist/cli/lib/commands/meta.js +0 -229
  13. package/dist/cli/lib/commands/params-groups.js +0 -48
  14. package/dist/cli/lib/commands/params-move.js +0 -44
  15. package/dist/cli/lib/commands/params.js +0 -314
  16. package/dist/cli/lib/commands/profile.js +0 -91
  17. package/dist/cli/lib/commands/setup.js +0 -294
  18. package/dist/cli/lib/constants.js +0 -30
  19. package/dist/cli/lib/help.js +0 -205
  20. package/dist/cli/lib/options-commands.js +0 -274
  21. package/dist/cli/lib/options-groups.js +0 -41
  22. package/dist/cli/lib/options.js +0 -125
  23. package/dist/cli/lib/output.js +0 -147
  24. package/dist/cli/lib/pagination.js +0 -55
  25. package/dist/cli/lib/policy-filter.js +0 -202
  26. package/dist/cli/lib/policy.js +0 -91
  27. package/dist/cli/lib/report.js +0 -61
  28. package/dist/cli/lib/response.js +0 -235
  29. package/dist/cli/lib/scope.js +0 -250
  30. package/dist/cli/lib/snapshot.js +0 -216
  31. package/dist/cli/lib/types.js +0 -2
  32. package/dist/cli/tabctl.js +0 -475
  33. package/dist/extension/lib/archive.js +0 -444
  34. package/dist/extension/lib/deps.js +0 -4
  35. package/dist/extension/lib/groups.js +0 -529
  36. package/dist/extension/lib/inspect.js +0 -252
  37. package/dist/extension/lib/move.js +0 -342
  38. package/dist/extension/lib/tabs.js +0 -456
  39. package/dist/extension/lib/undo-handlers.js +0 -447
  40. package/dist/host/host.bundle.js +0 -670
  41. package/dist/host/host.js +0 -143
  42. package/dist/host/host.sh +0 -5
  43. package/dist/host/launcher/go.mod +0 -3
  44. package/dist/host/launcher/main.go +0 -109
  45. package/dist/host/lib/handlers.js +0 -327
  46. package/dist/host/lib/undo.js +0 -60
  47. package/dist/shared/config.js +0 -134
  48. package/dist/shared/extension-sync.js +0 -170
  49. package/dist/shared/profiles.js +0 -78
  50. package/dist/shared/version.js +0 -8
  51. package/dist/shared/wrapper-health.js +0 -132
@@ -1,447 +0,0 @@
1
- "use strict";
2
- // Undo transaction handlers — extracted from background.ts (pure structural refactor).
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.undoGroupUpdate = undoGroupUpdate;
5
- exports.undoGroupUngroup = undoGroupUngroup;
6
- exports.undoGroupAssign = undoGroupAssign;
7
- exports.undoGroupGather = undoGroupGather;
8
- exports.undoMoveTab = undoMoveTab;
9
- exports.undoMoveGroup = undoMoveGroup;
10
- exports.undoMergeWindow = undoMergeWindow;
11
- exports.undoArchive = undoArchive;
12
- exports.undoClose = undoClose;
13
- exports.undoTransaction = undoTransaction;
14
- async function ensureWindow(windowId) {
15
- if (windowId) {
16
- try {
17
- const existing = await chrome.windows.get(windowId);
18
- if (existing) {
19
- return windowId;
20
- }
21
- }
22
- catch {
23
- // fall through to create
24
- }
25
- }
26
- const created = await chrome.windows.create({ focused: false });
27
- return created.id;
28
- }
29
- async function restoreTabsFromUndo(entries, deps) {
30
- const skipped = [];
31
- const restored = [];
32
- const windowMap = new Map();
33
- for (const entry of entries) {
34
- const tabId = Number(entry.tabId);
35
- if (!Number.isFinite(tabId)) {
36
- skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
37
- continue;
38
- }
39
- const sourceWindowId = Number(entry.windowId);
40
- if (!Number.isFinite(sourceWindowId)) {
41
- skipped.push({ tabId, reason: "missing_window" });
42
- continue;
43
- }
44
- let targetWindowId = windowMap.get(sourceWindowId);
45
- if (!targetWindowId) {
46
- targetWindowId = await ensureWindow(sourceWindowId);
47
- windowMap.set(sourceWindowId, targetWindowId);
48
- }
49
- try {
50
- await chrome.tabs.move(tabId, { windowId: targetWindowId, index: -1 });
51
- restored.push({ tabId, entry, targetWindowId });
52
- }
53
- catch {
54
- skipped.push({ tabId, reason: "move_failed" });
55
- }
56
- }
57
- const groupsByWindow = new Map();
58
- for (const item of restored) {
59
- const entry = item.entry;
60
- const rawGroupId = typeof entry.groupId === "number" ? entry.groupId : null;
61
- const groupId = rawGroupId != null && rawGroupId !== -1 ? rawGroupId : null;
62
- const groupTitle = typeof entry.groupTitle === "string" ? entry.groupTitle : null;
63
- if (!groupId && !groupTitle) {
64
- continue;
65
- }
66
- const groupKey = groupId != null ? `id:${groupId}` : `title:${groupTitle}`;
67
- const key = `${item.targetWindowId}:${groupKey}`;
68
- if (!groupsByWindow.has(key)) {
69
- groupsByWindow.set(key, {
70
- windowId: item.targetWindowId,
71
- groupId,
72
- groupTitle,
73
- groupColor: typeof entry.groupColor === "string" ? entry.groupColor : null,
74
- groupCollapsed: typeof entry.groupCollapsed === "boolean" ? entry.groupCollapsed : null,
75
- tabIds: [],
76
- });
77
- }
78
- groupsByWindow.get(key)?.tabIds.push(item.tabId);
79
- }
80
- for (const group of groupsByWindow.values()) {
81
- if (group.tabIds.length === 0) {
82
- continue;
83
- }
84
- let targetGroupId = null;
85
- if (group.groupId != null) {
86
- try {
87
- targetGroupId = await chrome.tabs.group({ groupId: group.groupId, tabIds: group.tabIds });
88
- }
89
- catch {
90
- targetGroupId = null;
91
- }
92
- }
93
- if (targetGroupId == null) {
94
- try {
95
- targetGroupId = await chrome.tabs.group({
96
- tabIds: group.tabIds,
97
- createProperties: { windowId: group.windowId },
98
- });
99
- }
100
- catch (error) {
101
- deps.log("Failed to regroup tabs", error);
102
- continue;
103
- }
104
- }
105
- const update = {};
106
- if (typeof group.groupTitle === "string") {
107
- update.title = group.groupTitle;
108
- }
109
- if (typeof group.groupColor === "string" && group.groupColor) {
110
- update.color = group.groupColor;
111
- }
112
- if (typeof group.groupCollapsed === "boolean") {
113
- update.collapsed = group.groupCollapsed;
114
- }
115
- if (Object.keys(update).length > 0) {
116
- try {
117
- await chrome.tabGroups.update(targetGroupId, update);
118
- }
119
- catch (error) {
120
- deps.log("Failed to update restored group", error);
121
- }
122
- }
123
- }
124
- const orderByWindow = new Map();
125
- for (const item of restored) {
126
- const index = Number(item.entry.index);
127
- if (!Number.isFinite(index)) {
128
- continue;
129
- }
130
- if (!orderByWindow.has(item.targetWindowId)) {
131
- orderByWindow.set(item.targetWindowId, []);
132
- }
133
- orderByWindow.get(item.targetWindowId)?.push({ tabId: item.tabId, index });
134
- }
135
- for (const [targetWindowId, items] of orderByWindow.entries()) {
136
- const ordered = [...items].sort((a, b) => a.index - b.index);
137
- for (const item of ordered) {
138
- try {
139
- await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.index });
140
- }
141
- catch {
142
- // ignore ordering failures
143
- }
144
- }
145
- }
146
- return {
147
- summary: {
148
- restoredTabs: restored.length,
149
- skippedTabs: skipped.length,
150
- },
151
- skipped,
152
- };
153
- }
154
- async function undoGroupUpdate(undo) {
155
- const groupId = Number(undo.groupId);
156
- if (!Number.isFinite(groupId)) {
157
- return {
158
- summary: { restoredGroups: 0, skippedGroups: 1 },
159
- skipped: [{ groupId: undo.groupId, reason: "missing_group" }],
160
- };
161
- }
162
- const previous = undo.previous || {};
163
- const update = {};
164
- if (typeof previous.title === "string") {
165
- update.title = previous.title;
166
- }
167
- if (typeof previous.color === "string" && previous.color) {
168
- update.color = previous.color;
169
- }
170
- if (typeof previous.collapsed === "boolean") {
171
- update.collapsed = previous.collapsed;
172
- }
173
- if (!Object.keys(update).length) {
174
- return {
175
- summary: { restoredGroups: 0, skippedGroups: 1 },
176
- skipped: [{ groupId, reason: "missing_values" }],
177
- };
178
- }
179
- try {
180
- await chrome.tabGroups.update(groupId, update);
181
- return {
182
- summary: { restoredGroups: 1, skippedGroups: 0 },
183
- skipped: [],
184
- };
185
- }
186
- catch {
187
- return {
188
- summary: { restoredGroups: 0, skippedGroups: 1 },
189
- skipped: [{ groupId, reason: "update_failed" }],
190
- };
191
- }
192
- }
193
- async function undoGroupUngroup(undo, deps) {
194
- const tabs = undo.tabs || [];
195
- return await restoreTabsFromUndo(tabs, deps);
196
- }
197
- async function undoGroupAssign(undo, deps) {
198
- const tabs = undo.tabs || [];
199
- return await restoreTabsFromUndo(tabs, deps);
200
- }
201
- async function undoGroupGather(undo, deps) {
202
- const tabs = undo.tabs || [];
203
- return await restoreTabsFromUndo(tabs, deps);
204
- }
205
- async function undoMoveTab(undo, deps) {
206
- const from = undo.from || {};
207
- const entry = {
208
- tabId: undo.tabId,
209
- windowId: from.windowId,
210
- index: from.index,
211
- groupId: from.groupId,
212
- groupTitle: from.groupTitle,
213
- groupColor: from.groupColor,
214
- groupCollapsed: from.groupCollapsed,
215
- };
216
- return await restoreTabsFromUndo([entry], deps);
217
- }
218
- async function undoMoveGroup(undo, deps) {
219
- const tabs = undo.tabs || [];
220
- return await restoreTabsFromUndo(tabs, deps);
221
- }
222
- async function undoMergeWindow(undo, deps) {
223
- const tabs = undo.tabs || [];
224
- return await restoreTabsFromUndo(tabs, deps);
225
- }
226
- async function undoArchive(undo, deps) {
227
- const tabs = undo.tabs || [];
228
- const restored = [];
229
- const skipped = [];
230
- const windowMap = new Map();
231
- for (const entry of tabs) {
232
- if (!entry.tabId) {
233
- skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
234
- continue;
235
- }
236
- let targetWindowId = windowMap.get(entry.from.windowId);
237
- if (!targetWindowId) {
238
- targetWindowId = await ensureWindow(entry.from.windowId);
239
- windowMap.set(entry.from.windowId, targetWindowId);
240
- }
241
- try {
242
- await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: -1 });
243
- restored.push({ tabId: entry.tabId, targetWindowId });
244
- }
245
- catch {
246
- skipped.push({ tabId: entry.tabId, reason: "move_failed" });
247
- }
248
- }
249
- const restoredSet = new Set(restored.map((item) => item.tabId));
250
- const groupsByWindow = new Map();
251
- for (const entry of tabs) {
252
- if (!restoredSet.has(entry.tabId)) {
253
- continue;
254
- }
255
- const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
256
- const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
257
- const hasGroupTitle = entry.from.groupTitle != null;
258
- if (!hasGroupId && !hasGroupTitle) {
259
- continue;
260
- }
261
- const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
262
- const key = `${targetWindowId}:${groupKey}`;
263
- if (!groupsByWindow.has(key)) {
264
- groupsByWindow.set(key, []);
265
- }
266
- groupsByWindow.get(key)?.push(entry);
267
- }
268
- for (const [key, groupTabs] of groupsByWindow.entries()) {
269
- const [windowIdPart] = key.split(":");
270
- const targetWindowId = Number(windowIdPart);
271
- const tabIds = groupTabs.map((entry) => entry.tabId).filter(Boolean);
272
- if (!tabIds.length) {
273
- continue;
274
- }
275
- try {
276
- const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
277
- await chrome.tabGroups.update(groupId, {
278
- title: groupTabs[0].from.groupTitle || "",
279
- color: groupTabs[0].from.groupColor || "grey",
280
- collapsed: groupTabs[0].from.groupCollapsed || false,
281
- });
282
- }
283
- catch (error) {
284
- deps.log("Failed to recreate group", error);
285
- }
286
- }
287
- for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
288
- const windowTabs = tabs
289
- .filter((entry) => entry.from.windowId === originalWindowId && restoredSet.has(entry.tabId))
290
- .sort((a, b) => a.from.index - b.from.index);
291
- for (const entry of windowTabs) {
292
- if (!entry.tabId) {
293
- continue;
294
- }
295
- try {
296
- await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: entry.from.index });
297
- }
298
- catch {
299
- // ignore ordering failures
300
- }
301
- }
302
- const activeTab = windowTabs.find((entry) => entry.active);
303
- if (activeTab && activeTab.tabId) {
304
- try {
305
- await chrome.tabs.update(activeTab.tabId, { active: true });
306
- }
307
- catch {
308
- // ignore
309
- }
310
- }
311
- }
312
- return {
313
- summary: {
314
- restoredTabs: restored.length,
315
- skippedTabs: skipped.length,
316
- },
317
- skipped,
318
- };
319
- }
320
- async function undoClose(undo, deps) {
321
- const tabs = undo.tabs || [];
322
- const restored = [];
323
- const skipped = [];
324
- const windowMap = new Map();
325
- for (const entry of tabs) {
326
- if (!entry.url) {
327
- skipped.push({ url: entry.url, reason: "missing_url" });
328
- continue;
329
- }
330
- let targetWindowId = windowMap.get(entry.from.windowId);
331
- if (!targetWindowId) {
332
- targetWindowId = await ensureWindow(entry.from.windowId);
333
- windowMap.set(entry.from.windowId, targetWindowId);
334
- }
335
- try {
336
- const created = await chrome.tabs.create({
337
- windowId: targetWindowId,
338
- url: entry.url,
339
- active: false,
340
- pinned: entry.pinned,
341
- });
342
- restored.push({ tabId: created.id, entry });
343
- }
344
- catch {
345
- skipped.push({ url: entry.url, reason: "create_failed" });
346
- }
347
- }
348
- const groupsByWindow = new Map();
349
- for (const item of restored) {
350
- const entry = item.entry;
351
- const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
352
- const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
353
- const hasGroupTitle = entry.from.groupTitle != null;
354
- if (!hasGroupId && !hasGroupTitle) {
355
- continue;
356
- }
357
- const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
358
- const key = `${targetWindowId}:${groupKey}`;
359
- if (!groupsByWindow.has(key)) {
360
- groupsByWindow.set(key, []);
361
- }
362
- groupsByWindow.get(key)?.push({ tabId: item.tabId, entry });
363
- }
364
- for (const [key, groupTabs] of groupsByWindow.entries()) {
365
- const [windowIdPart] = key.split(":");
366
- const targetWindowId = Number(windowIdPart);
367
- const tabIds = groupTabs.map((item) => item.tabId).filter(Boolean);
368
- if (!tabIds.length) {
369
- continue;
370
- }
371
- try {
372
- const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
373
- await chrome.tabGroups.update(groupId, {
374
- title: groupTabs[0].entry.from.groupTitle || "",
375
- color: groupTabs[0].entry.from.groupColor || "grey",
376
- collapsed: groupTabs[0].entry.from.groupCollapsed || false,
377
- });
378
- }
379
- catch (error) {
380
- deps.log("Failed to recreate group", error);
381
- }
382
- }
383
- for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
384
- const windowTabs = restored
385
- .map((item) => ({ tabId: item.tabId, entry: item.entry }))
386
- .filter((item) => item.entry.from.windowId === originalWindowId)
387
- .sort((a, b) => a.entry.from.index - b.entry.from.index);
388
- for (const item of windowTabs) {
389
- try {
390
- await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.entry.from.index });
391
- }
392
- catch {
393
- // ignore ordering failures
394
- }
395
- }
396
- const activeTab = windowTabs.find((item) => item.entry.active);
397
- if (activeTab) {
398
- try {
399
- await chrome.tabs.update(activeTab.tabId, { active: true });
400
- }
401
- catch {
402
- // ignore
403
- }
404
- }
405
- }
406
- return {
407
- summary: {
408
- restoredTabs: restored.length,
409
- skippedTabs: skipped.length,
410
- },
411
- skipped,
412
- };
413
- }
414
- async function undoTransaction(params, deps) {
415
- if (!params.record || !params.record.undo) {
416
- throw new Error("Undo record missing");
417
- }
418
- const undo = params.record.undo;
419
- if (undo.action === "archive") {
420
- return await undoArchive(undo, deps);
421
- }
422
- if (undo.action === "close") {
423
- return await undoClose(undo, deps);
424
- }
425
- if (undo.action === "group-update") {
426
- return await undoGroupUpdate(undo);
427
- }
428
- if (undo.action === "group-ungroup") {
429
- return await undoGroupUngroup(undo, deps);
430
- }
431
- if (undo.action === "group-assign") {
432
- return await undoGroupAssign(undo, deps);
433
- }
434
- if (undo.action === "group-gather") {
435
- return await undoGroupGather(undo, deps);
436
- }
437
- if (undo.action === "move-tab") {
438
- return await undoMoveTab(undo, deps);
439
- }
440
- if (undo.action === "move-group") {
441
- return await undoMoveGroup(undo, deps);
442
- }
443
- if (undo.action === "merge-window") {
444
- return await undoMergeWindow(undo, deps);
445
- }
446
- throw new Error(`Unknown undo action: ${undo.action}`);
447
- }