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,529 +0,0 @@
1
- "use strict";
2
- // Group management — extracted from background.ts (pure structural refactor).
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.getGroupTabs = getGroupTabs;
5
- exports.listGroupSummaries = listGroupSummaries;
6
- exports.summarizeGroupMatch = summarizeGroupMatch;
7
- exports.findGroupMatches = findGroupMatches;
8
- exports.resolveGroupByTitle = resolveGroupByTitle;
9
- exports.resolveGroupById = resolveGroupById;
10
- exports.listGroups = listGroups;
11
- exports.groupUpdate = groupUpdate;
12
- exports.groupUngroup = groupUngroup;
13
- exports.groupAssign = groupAssign;
14
- exports.groupGather = groupGather;
15
- function getGroupTabs(windowSnapshot, groupId) {
16
- return windowSnapshot.tabs
17
- .filter((tab) => tab.groupId === groupId)
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
- });
23
- }
24
- function listGroupSummaries(snapshot, buildWindowLabels, windowId) {
25
- const windowLabels = buildWindowLabels(snapshot);
26
- const summaries = [];
27
- const windows = snapshot.windows;
28
- for (const win of windows) {
29
- if (windowId && win.windowId !== windowId) {
30
- continue;
31
- }
32
- for (const group of win.groups) {
33
- summaries.push({
34
- windowId: win.windowId,
35
- windowLabel: windowLabels.get(win.windowId) ?? null,
36
- groupId: group.groupId,
37
- title: typeof group.title === "string" ? group.title : null,
38
- });
39
- }
40
- }
41
- return summaries;
42
- }
43
- function summarizeGroupMatch(match, windowLabels) {
44
- return {
45
- windowId: match.windowId,
46
- windowLabel: windowLabels.get(match.windowId) ?? null,
47
- groupId: match.group.groupId,
48
- title: typeof match.group.title === "string" ? match.group.title : null,
49
- };
50
- }
51
- function findGroupMatches(snapshot, groupTitle, windowId) {
52
- const matches = [];
53
- const windows = snapshot.windows;
54
- for (const win of windows) {
55
- if (windowId && win.windowId !== windowId) {
56
- continue;
57
- }
58
- for (const group of win.groups) {
59
- if (group.title === groupTitle) {
60
- matches.push({
61
- windowId: win.windowId,
62
- group,
63
- tabs: getGroupTabs(win, group.groupId),
64
- });
65
- }
66
- }
67
- }
68
- return matches;
69
- }
70
- function resolveGroupByTitle(snapshot, buildWindowLabels, groupTitle, windowId) {
71
- const windowLabels = buildWindowLabels(snapshot);
72
- const allMatches = findGroupMatches(snapshot, groupTitle);
73
- const matches = windowId ? allMatches.filter((match) => match.windowId === windowId) : allMatches;
74
- const availableGroups = listGroupSummaries(snapshot, buildWindowLabels);
75
- if (matches.length === 0) {
76
- const message = windowId && allMatches.length > 0
77
- ? "Group title not found in specified window"
78
- : "No matching group title found";
79
- return {
80
- error: {
81
- message,
82
- hint: "Use tabctl group-list to see existing groups.",
83
- matches: allMatches.map((match) => summarizeGroupMatch(match, windowLabels)),
84
- availableGroups,
85
- },
86
- };
87
- }
88
- if (matches.length > 1) {
89
- return {
90
- error: {
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.",
93
- matches: matches.map((match) => summarizeGroupMatch(match, windowLabels)),
94
- availableGroups,
95
- },
96
- };
97
- }
98
- return { match: matches[0] };
99
- }
100
- function resolveGroupById(snapshot, buildWindowLabels, groupId) {
101
- const windows = snapshot.windows;
102
- const matches = [];
103
- for (const win of windows) {
104
- const group = win.groups.find((entry) => entry.groupId === groupId);
105
- if (group) {
106
- matches.push({
107
- windowId: win.windowId,
108
- group,
109
- tabs: getGroupTabs(win, groupId),
110
- });
111
- }
112
- }
113
- if (matches.length === 0) {
114
- return {
115
- error: {
116
- message: "Group not found",
117
- hint: "Use tabctl group-list to see existing groups.",
118
- availableGroups: listGroupSummaries(snapshot, buildWindowLabels),
119
- },
120
- };
121
- }
122
- if (matches.length > 1) {
123
- const windowLabels = buildWindowLabels(snapshot);
124
- return {
125
- error: {
126
- message: "Group id is ambiguous. Provide a windowId.",
127
- hint: "Use --window to disambiguate group ids.",
128
- matches: matches.map((match) => summarizeGroupMatch(match, windowLabels)),
129
- availableGroups: listGroupSummaries(snapshot, buildWindowLabels),
130
- },
131
- };
132
- }
133
- return { match: matches[0] };
134
- }
135
- async function listGroups(params, deps) {
136
- const snapshot = await deps.getTabSnapshot();
137
- const windows = snapshot.windows;
138
- const windowLabels = deps.buildWindowLabels(snapshot);
139
- const windowIdParam = params.windowId != null ? deps.resolveWindowIdFromParams(snapshot, params.windowId) : null;
140
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
141
- throw new Error("Window not found");
142
- }
143
- const groups = [];
144
- for (const win of windows) {
145
- if (windowIdParam && win.windowId !== windowIdParam) {
146
- continue;
147
- }
148
- const counts = new Map();
149
- for (const tab of win.tabs) {
150
- const groupId = tab.groupId;
151
- if (typeof groupId === "number" && groupId !== -1) {
152
- counts.set(groupId, (counts.get(groupId) || 0) + 1);
153
- }
154
- }
155
- for (const group of win.groups) {
156
- const groupId = group.groupId;
157
- groups.push({
158
- windowId: win.windowId,
159
- windowLabel: windowLabels.get(win.windowId) ?? null,
160
- groupId,
161
- title: group.title ?? null,
162
- color: group.color ?? null,
163
- collapsed: group.collapsed ?? null,
164
- tabCount: counts.get(groupId) || 0,
165
- });
166
- }
167
- }
168
- return { groups };
169
- }
170
- async function groupUpdate(params, deps) {
171
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
172
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
173
- if (!groupId && !groupTitle) {
174
- throw new Error("Missing group identifier");
175
- }
176
- const snapshot = await deps.getTabSnapshot();
177
- const windows = snapshot.windows;
178
- const windowIdParam = params.windowId != null ? deps.resolveWindowIdFromParams(snapshot, params.windowId) : null;
179
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
180
- throw new Error("Window not found");
181
- }
182
- let match;
183
- if (groupId != null) {
184
- const resolved = resolveGroupById(snapshot, deps.buildWindowLabels, groupId);
185
- if (resolved.error) {
186
- throw resolved.error;
187
- }
188
- match = resolved.match;
189
- if (windowIdParam && windowIdParam !== match.windowId) {
190
- throw new Error("Group is not in the specified window");
191
- }
192
- }
193
- else {
194
- const resolved = resolveGroupByTitle(snapshot, deps.buildWindowLabels, groupTitle, windowIdParam || undefined);
195
- if (resolved.error) {
196
- throw resolved.error;
197
- }
198
- match = resolved.match;
199
- }
200
- const update = {};
201
- if (typeof params.title === "string") {
202
- update.title = params.title;
203
- }
204
- if (typeof params.color === "string" && params.color.trim()) {
205
- update.color = params.color.trim();
206
- }
207
- if (typeof params.collapsed === "boolean") {
208
- update.collapsed = params.collapsed;
209
- }
210
- if (!Object.keys(update).length) {
211
- throw new Error("Missing group update fields");
212
- }
213
- const updated = await chrome.tabGroups.update(match.group.groupId, update);
214
- return {
215
- groupId: updated.id,
216
- windowId: updated.windowId,
217
- title: updated.title,
218
- color: updated.color,
219
- collapsed: updated.collapsed,
220
- undo: {
221
- action: "group-update",
222
- groupId: updated.id,
223
- windowId: match.windowId,
224
- previous: {
225
- title: match.group.title ?? null,
226
- color: match.group.color ?? null,
227
- collapsed: match.group.collapsed ?? null,
228
- },
229
- },
230
- txid: params.txid || null,
231
- };
232
- }
233
- async function groupUngroup(params, deps) {
234
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
235
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
236
- if (!groupId && !groupTitle) {
237
- throw new Error("Missing group identifier");
238
- }
239
- const snapshot = await deps.getTabSnapshot();
240
- const windows = snapshot.windows;
241
- const windowIdParam = params.windowId != null ? deps.resolveWindowIdFromParams(snapshot, params.windowId) : null;
242
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
243
- throw new Error("Window not found");
244
- }
245
- let match;
246
- if (groupId != null) {
247
- const resolved = resolveGroupById(snapshot, deps.buildWindowLabels, groupId);
248
- if (resolved.error) {
249
- throw resolved.error;
250
- }
251
- match = resolved.match;
252
- if (windowIdParam && windowIdParam !== match.windowId) {
253
- throw new Error("Group is not in the specified window");
254
- }
255
- }
256
- else {
257
- const resolved = resolveGroupByTitle(snapshot, deps.buildWindowLabels, groupTitle, windowIdParam || undefined);
258
- if (resolved.error) {
259
- throw resolved.error;
260
- }
261
- match = resolved.match;
262
- }
263
- const undoTabs = match.tabs
264
- .map((tab) => ({
265
- tabId: tab.tabId,
266
- windowId: tab.windowId,
267
- index: tab.index,
268
- groupId: tab.groupId,
269
- groupTitle: tab.groupTitle,
270
- groupColor: tab.groupColor,
271
- groupCollapsed: match.group.collapsed ?? null,
272
- }))
273
- .filter((tab) => typeof tab.tabId === "number");
274
- const tabIds = match.tabs
275
- .map((tab) => tab.tabId)
276
- .filter((tabId) => typeof tabId === "number");
277
- if (tabIds.length) {
278
- await chrome.tabs.ungroup(tabIds);
279
- }
280
- return {
281
- groupId: match.group.groupId,
282
- groupTitle: match.group.title || null,
283
- windowId: match.windowId,
284
- summary: {
285
- ungroupedTabs: tabIds.length,
286
- },
287
- undo: {
288
- action: "group-ungroup",
289
- groupId: match.group.groupId,
290
- windowId: match.windowId,
291
- groupTitle: match.group.title || null,
292
- groupColor: match.group.color || null,
293
- groupCollapsed: match.group.collapsed ?? null,
294
- tabs: undoTabs,
295
- },
296
- txid: params.txid || null,
297
- };
298
- }
299
- async function groupAssign(params, deps) {
300
- const rawTabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
301
- const tabIds = rawTabIds.filter((id) => Number.isFinite(id));
302
- if (!tabIds.length) {
303
- throw new Error("Missing tabIds");
304
- }
305
- const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
306
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
307
- if (!groupId && !groupTitle) {
308
- throw new Error("Missing group identifier");
309
- }
310
- const snapshot = await deps.getTabSnapshot();
311
- const windows = snapshot.windows;
312
- const windowIdParam = params.windowId != null ? deps.resolveWindowIdFromParams(snapshot, params.windowId) : null;
313
- if (windowIdParam && !windows.some((win) => win.windowId === windowIdParam)) {
314
- throw new Error("Window not found");
315
- }
316
- const tabIndex = new Map();
317
- for (const win of windows) {
318
- for (const tab of win.tabs) {
319
- if (typeof tab.tabId === "number") {
320
- tabIndex.set(tab.tabId, { tab, windowId: win.windowId });
321
- }
322
- }
323
- }
324
- const skipped = [];
325
- const resolvedTabIds = [];
326
- const sourceWindows = new Set();
327
- const undoTabs = [];
328
- for (const tabId of tabIds) {
329
- const entry = tabIndex.get(tabId);
330
- if (!entry) {
331
- skipped.push({ tabId, reason: "not_found" });
332
- continue;
333
- }
334
- resolvedTabIds.push(tabId);
335
- sourceWindows.add(entry.windowId);
336
- const tab = entry.tab;
337
- undoTabs.push({
338
- tabId,
339
- windowId: entry.windowId,
340
- index: tab.index,
341
- groupId: tab.groupId,
342
- groupTitle: tab.groupTitle,
343
- groupColor: tab.groupColor,
344
- groupCollapsed: tab.groupCollapsed ?? null,
345
- });
346
- }
347
- if (!resolvedTabIds.length) {
348
- throw new Error("No matching tabs found");
349
- }
350
- let targetGroupId = null;
351
- let targetWindowId = null;
352
- let targetTitle = null;
353
- let created = false;
354
- if (groupId != null) {
355
- const resolved = resolveGroupById(snapshot, deps.buildWindowLabels, groupId);
356
- if (resolved.error) {
357
- throw resolved.error;
358
- }
359
- const match = resolved.match;
360
- targetGroupId = match.group.groupId;
361
- targetWindowId = match.windowId;
362
- targetTitle = typeof match.group.title === "string" ? match.group.title : null;
363
- if (windowIdParam && windowIdParam !== targetWindowId) {
364
- throw new Error("Group is not in the specified window");
365
- }
366
- }
367
- else {
368
- const resolved = resolveGroupByTitle(snapshot, deps.buildWindowLabels, groupTitle, windowIdParam || undefined);
369
- if (resolved.error) {
370
- const error = resolved.error;
371
- if (error.message === "No matching group title found" && params.create === true) {
372
- targetWindowId = windowIdParam || (sourceWindows.size === 1 ? Array.from(sourceWindows)[0] : null);
373
- if (!targetWindowId) {
374
- throw new Error("Multiple source windows. Provide --window to create a new group.");
375
- }
376
- targetTitle = groupTitle;
377
- created = true;
378
- }
379
- else {
380
- throw error;
381
- }
382
- }
383
- else {
384
- const match = resolved.match;
385
- targetGroupId = match.group.groupId;
386
- targetWindowId = match.windowId;
387
- targetTitle = typeof match.group.title === "string" && match.group.title ? match.group.title : groupTitle;
388
- }
389
- }
390
- if (!targetWindowId) {
391
- throw new Error("Target window not found");
392
- }
393
- const moveIds = resolvedTabIds.filter((tabId) => {
394
- const entry = tabIndex.get(tabId);
395
- return entry && entry.windowId !== targetWindowId;
396
- });
397
- if (moveIds.length > 0) {
398
- await chrome.tabs.move(moveIds, { windowId: targetWindowId, index: -1 });
399
- }
400
- let assignedGroupId = targetGroupId;
401
- if (targetGroupId != null) {
402
- await chrome.tabs.group({ groupId: targetGroupId, tabIds: resolvedTabIds });
403
- }
404
- else {
405
- assignedGroupId = await chrome.tabs.group({ tabIds: resolvedTabIds, createProperties: { windowId: targetWindowId } });
406
- const update = {};
407
- if (targetTitle) {
408
- update.title = targetTitle;
409
- }
410
- if (typeof params.color === "string" && params.color.trim()) {
411
- update.color = params.color.trim();
412
- }
413
- if (typeof params.collapsed === "boolean") {
414
- update.collapsed = params.collapsed;
415
- }
416
- if (Object.keys(update).length > 0) {
417
- try {
418
- await chrome.tabGroups.update(assignedGroupId, update);
419
- }
420
- catch (error) {
421
- deps.log("Failed to update group", error);
422
- }
423
- }
424
- created = true;
425
- }
426
- return {
427
- groupId: assignedGroupId,
428
- groupTitle: targetTitle || groupTitle || null,
429
- windowId: targetWindowId,
430
- created,
431
- summary: {
432
- movedTabs: moveIds.length,
433
- groupedTabs: resolvedTabIds.length,
434
- skippedTabs: skipped.length,
435
- },
436
- skipped,
437
- undo: {
438
- action: "group-assign",
439
- groupId: assignedGroupId,
440
- groupTitle: targetTitle || groupTitle || null,
441
- groupColor: typeof params.color === "string" && params.color.trim() ? params.color.trim() : null,
442
- groupCollapsed: typeof params.collapsed === "boolean" ? params.collapsed : null,
443
- created,
444
- tabs: undoTabs,
445
- },
446
- txid: params.txid || null,
447
- };
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
- }