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,456 +0,0 @@
1
- "use strict";
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
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.getMostRecentFocusedWindowId = getMostRecentFocusedWindowId;
8
- exports.normalizeUrl = normalizeUrl;
9
- exports.normalizeTabIndex = normalizeTabIndex;
10
- exports.resolveOpenWindow = resolveOpenWindow;
11
- exports.focusTab = focusTab;
12
- exports.refreshTabs = refreshTabs;
13
- exports.openTabs = openTabs;
14
- const normalize_url_1 = __importDefault(require("normalize-url"));
15
- function getMostRecentFocusedWindowId(windows) {
16
- let bestWindowId = null;
17
- let bestFocusedAt = -Infinity;
18
- for (const win of windows) {
19
- for (const tab of win.tabs) {
20
- const focusedAt = Number(tab.lastFocusedAt);
21
- if (!Number.isFinite(focusedAt)) {
22
- continue;
23
- }
24
- if (focusedAt > bestFocusedAt) {
25
- bestFocusedAt = focusedAt;
26
- bestWindowId = win.windowId;
27
- }
28
- }
29
- }
30
- return bestWindowId;
31
- }
32
- function normalizeUrl(rawUrl) {
33
- if (!rawUrl || typeof rawUrl !== "string") {
34
- return null;
35
- }
36
- try {
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
- });
52
- }
53
- catch {
54
- return null;
55
- }
56
- }
57
- function normalizeTabIndex(value) {
58
- const index = Number(value);
59
- return Number.isFinite(index) ? index : null;
60
- }
61
- function matchIncludes(value, needle) {
62
- if (!needle) {
63
- return false;
64
- }
65
- return typeof value === "string" && value.toLowerCase().includes(needle);
66
- }
67
- function resolveOpenWindow(snapshot, params) {
68
- const windows = snapshot.windows;
69
- if (!windows.length) {
70
- return { error: { message: "No windows available" } };
71
- }
72
- if (params.windowId != null) {
73
- if (typeof params.windowId === "string") {
74
- const normalized = params.windowId.trim().toLowerCase();
75
- if (normalized === "active") {
76
- const focused = windows.find((win) => win.focused);
77
- if (focused) {
78
- return { windowId: focused.windowId };
79
- }
80
- return { error: { message: "Active window not found" } };
81
- }
82
- if (normalized === "last-focused") {
83
- const lastFocused = getMostRecentFocusedWindowId(windows);
84
- if (lastFocused != null) {
85
- return { windowId: lastFocused };
86
- }
87
- return { error: { message: "Last focused window not found" } };
88
- }
89
- if (normalized === "new") {
90
- return { error: { message: "--window new is only supported by open" } };
91
- }
92
- }
93
- const windowId = Number(params.windowId);
94
- const found = windows.find((win) => win.windowId === windowId);
95
- if (!found) {
96
- return { error: { message: "Window not found" } };
97
- }
98
- return { windowId };
99
- }
100
- if (params.windowTabId != null) {
101
- const tabId = Number(params.windowTabId);
102
- const found = windows.find((win) => win.tabs.some((tab) => tab.tabId === tabId));
103
- if (!found) {
104
- return { error: { message: "Window not found for tab" } };
105
- }
106
- return { windowId: found.windowId };
107
- }
108
- let candidates = [...windows];
109
- let filtered = false;
110
- if (typeof params.afterGroupTitle === "string" && params.afterGroupTitle.trim()) {
111
- const groupTitle = params.afterGroupTitle.trim();
112
- candidates = candidates.filter((win) => win.groups.some((group) => group.title === groupTitle));
113
- filtered = true;
114
- }
115
- if (typeof params.windowGroupTitle === "string" && params.windowGroupTitle.trim()) {
116
- const groupTitle = params.windowGroupTitle.trim();
117
- candidates = candidates.filter((win) => win.groups.some((group) => group.title === groupTitle));
118
- filtered = true;
119
- }
120
- if (typeof params.windowUrl === "string" && params.windowUrl.trim()) {
121
- const needle = params.windowUrl.trim().toLowerCase();
122
- candidates = candidates.filter((win) => win.tabs.some((tab) => matchIncludes(tab.url, needle)));
123
- filtered = true;
124
- }
125
- if (filtered) {
126
- if (candidates.length === 1) {
127
- return { windowId: candidates[0].windowId };
128
- }
129
- if (candidates.length === 0) {
130
- return { error: { message: "No matching window found" } };
131
- }
132
- return { error: { message: "Multiple windows match selection. Provide --window to disambiguate." } };
133
- }
134
- const focused = windows.find((win) => win.focused);
135
- if (focused) {
136
- return { windowId: focused.windowId };
137
- }
138
- if (windows.length === 1) {
139
- return { windowId: windows[0].windowId };
140
- }
141
- const lastFocused = getMostRecentFocusedWindowId(windows);
142
- if (lastFocused != null) {
143
- return { windowId: lastFocused };
144
- }
145
- return { error: { message: "Multiple windows available. Provide --window to target one." } };
146
- }
147
- async function focusTab(params) {
148
- const tabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
149
- const tabId = Number.isFinite(params.tabId)
150
- ? Number(params.tabId)
151
- : tabIds.length
152
- ? Number(tabIds[0])
153
- : null;
154
- if (!tabId) {
155
- throw new Error("Missing tabId");
156
- }
157
- const tab = await chrome.tabs.get(tabId);
158
- await chrome.windows.update(tab.windowId, { focused: true });
159
- await chrome.tabs.update(tabId, { active: true });
160
- return {
161
- tabId,
162
- windowId: tab.windowId,
163
- };
164
- }
165
- async function refreshTabs(params) {
166
- const tabId = Number.isFinite(params.tabId)
167
- ? Number(params.tabId)
168
- : null;
169
- if (!tabId) {
170
- throw new Error("Missing tabId");
171
- }
172
- await chrome.tabs.reload(tabId);
173
- return {
174
- tabId,
175
- summary: { refreshedTabs: 1 },
176
- };
177
- }
178
- async function openTabs(params, deps) {
179
- const urls = Array.isArray(params.urls)
180
- ? params.urls.map((url) => (typeof url === "string" ? url.trim() : "")).filter(Boolean)
181
- : [];
182
- const groupTitle = typeof params.groupTitle === "string" ? params.groupTitle.trim() : "";
183
- const groupColor = typeof params.color === "string" ? params.color.trim() : "";
184
- const afterGroupTitle = typeof params.afterGroupTitle === "string" ? params.afterGroupTitle.trim() : "";
185
- const beforeTabId = Number.isFinite(params.beforeTabId) ? Number(params.beforeTabId) : null;
186
- const afterTabId = Number.isFinite(params.afterTabId) ? Number(params.afterTabId) : null;
187
- if (beforeTabId != null && afterTabId != null) {
188
- throw new Error("Only one target position is allowed");
189
- }
190
- const newWindow = params.newWindow === true;
191
- const forceNewGroup = params.newGroup === true;
192
- const allowDuplicates = params.allowDuplicates === true;
193
- if (!urls.length && !newWindow) {
194
- throw new Error("No URLs provided");
195
- }
196
- if (newWindow) {
197
- if (afterGroupTitle || beforeTabId || afterTabId) {
198
- throw new Error("Cannot use --before/--after with --new-window");
199
- }
200
- if (params.windowId != null || params.windowGroupTitle || params.windowTabId != null || params.windowUrl) {
201
- throw new Error("Cannot combine --new-window with window selectors");
202
- }
203
- const created = [];
204
- const skipped = [];
205
- const createdWindow = await chrome.windows.create({ focused: false });
206
- const windowId = createdWindow.id;
207
- let seedTabs = createdWindow.tabs;
208
- if (!seedTabs) {
209
- seedTabs = await chrome.tabs.query({ windowId });
210
- }
211
- const seedTabId = seedTabs.find((tab) => typeof tab.id === "number")?.id ?? null;
212
- for (const url of urls) {
213
- try {
214
- const tab = await chrome.tabs.create({ windowId, url, active: false });
215
- created.push({
216
- tabId: tab.id,
217
- windowId: tab.windowId,
218
- index: tab.index,
219
- url: tab.url,
220
- title: tab.title,
221
- });
222
- }
223
- catch (error) {
224
- skipped.push({ url, reason: "create_failed" });
225
- }
226
- }
227
- if (!urls.length && seedTabs.length) {
228
- const tab = seedTabs[0];
229
- created.push({
230
- tabId: tab.id,
231
- windowId: tab.windowId,
232
- index: tab.index,
233
- url: tab.url,
234
- title: tab.title,
235
- });
236
- }
237
- if (seedTabId && created.length > 0 && urls.length > 0) {
238
- try {
239
- await chrome.tabs.remove(seedTabId);
240
- }
241
- catch (error) {
242
- deps.log("Failed to remove seed tab", error);
243
- }
244
- }
245
- let groupId = null;
246
- if (groupTitle && created.length > 0) {
247
- try {
248
- const tabIds = created.map((tab) => tab.tabId).filter((id) => typeof id === "number");
249
- if (tabIds.length > 0) {
250
- groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId } });
251
- const update = { title: groupTitle };
252
- if (groupColor) {
253
- update.color = groupColor;
254
- }
255
- await chrome.tabGroups.update(groupId, update);
256
- }
257
- }
258
- catch (error) {
259
- deps.log("Failed to create group", error);
260
- groupId = null;
261
- }
262
- }
263
- return {
264
- windowId,
265
- groupId,
266
- groupTitle: groupTitle || null,
267
- afterGroupTitle: null,
268
- insertIndex: null,
269
- created,
270
- skipped,
271
- summary: {
272
- createdTabs: created.length,
273
- skippedUrls: skipped.length,
274
- grouped: Boolean(groupId),
275
- },
276
- };
277
- }
278
- const snapshot = await deps.getTabSnapshot();
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
- }
287
- if (params.windowId == null && (beforeTabId != null || afterTabId != null)) {
288
- const anchorId = beforeTabId != null ? beforeTabId : afterTabId;
289
- const anchorWindow = snapshot.windows
290
- .find((win) => win.tabs.some((tab) => tab.tabId === anchorId));
291
- if (anchorWindow) {
292
- openParams = { ...params, windowId: anchorWindow.windowId };
293
- }
294
- }
295
- const selection = resolveOpenWindow(snapshot, openParams);
296
- if (selection.error) {
297
- throw selection.error;
298
- }
299
- const windowId = selection.windowId;
300
- const windowSnapshot = snapshot.windows.find((win) => win.windowId === windowId);
301
- if (!windowSnapshot) {
302
- throw new Error("Window snapshot unavailable");
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
- }
323
- const created = [];
324
- const skipped = [];
325
- let insertIndex = null;
326
- if (afterGroupTitle) {
327
- const targetGroup = windowSnapshot.groups.find((group) => group.title === afterGroupTitle);
328
- if (!targetGroup) {
329
- throw new Error("Group not found in target window");
330
- }
331
- const groupTabs = windowSnapshot.tabs.filter((tab) => tab.groupId === targetGroup.groupId);
332
- if (!groupTabs.length) {
333
- throw new Error("Group has no tabs to anchor insertion");
334
- }
335
- const indices = groupTabs
336
- .map((tab) => normalizeTabIndex(tab.index))
337
- .filter((value) => value != null);
338
- if (!indices.length) {
339
- throw new Error("Group tabs missing indices");
340
- }
341
- insertIndex = Math.max(...indices) + 1;
342
- }
343
- if (beforeTabId != null || afterTabId != null) {
344
- if (afterGroupTitle) {
345
- throw new Error("Only one target position is allowed");
346
- }
347
- const anchorId = beforeTabId != null ? beforeTabId : afterTabId;
348
- const anchorTab = windowSnapshot.tabs.find((tab) => tab.tabId === anchorId);
349
- if (!anchorTab) {
350
- throw new Error("Anchor tab not found in target window");
351
- }
352
- const anchorIndex = normalizeTabIndex(anchorTab.index);
353
- if (!Number.isFinite(anchorIndex)) {
354
- throw new Error("Anchor tab index unavailable");
355
- }
356
- insertIndex = beforeTabId != null ? anchorIndex : anchorIndex + 1;
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
- }
368
- let nextIndex = insertIndex;
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
- }
377
- try {
378
- const createOptions = { windowId, url, active: false };
379
- if (nextIndex != null) {
380
- createOptions.index = nextIndex;
381
- nextIndex += 1;
382
- }
383
- const tab = await chrome.tabs.create(createOptions);
384
- created.push({
385
- tabId: tab.id,
386
- windowId: tab.windowId,
387
- index: tab.index,
388
- url: tab.url,
389
- title: tab.title,
390
- });
391
- }
392
- catch (error) {
393
- skipped.push({ url, reason: "create_failed" });
394
- }
395
- }
396
- let groupId = null;
397
- if (groupTitle && created.length > 0) {
398
- try {
399
- const tabIds = created.map((tab) => tab.tabId).filter((id) => typeof id === "number");
400
- if (tabIds.length > 0) {
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);
412
- }
413
- }
414
- }
415
- catch (error) {
416
- deps.log("Failed to create group", error);
417
- groupId = null;
418
- }
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
- }
442
- return {
443
- windowId,
444
- groupId,
445
- groupTitle: groupTitle || null,
446
- afterGroupTitle: afterGroupTitle || null,
447
- insertIndex,
448
- created,
449
- skipped,
450
- summary: {
451
- createdTabs: created.length,
452
- skippedUrls: skipped.length,
453
- grouped: Boolean(groupId),
454
- },
455
- };
456
- }