tabctl 0.1.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.
@@ -0,0 +1,378 @@
1
+ "use strict";
2
+ /**
3
+ * Command parameter builders.
4
+ * These functions extract and validate command parameters from CLI options.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.buildAnalyzeParams = buildAnalyzeParams;
11
+ exports.buildInspectParams = buildInspectParams;
12
+ exports.buildFocusParams = buildFocusParams;
13
+ exports.buildRefreshParams = buildRefreshParams;
14
+ exports.buildOpenParams = buildOpenParams;
15
+ exports.buildGroupUpdateParams = buildGroupUpdateParams;
16
+ exports.buildGroupUngroupParams = buildGroupUngroupParams;
17
+ exports.buildGroupAssignParams = buildGroupAssignParams;
18
+ exports.buildMoveTabParams = buildMoveTabParams;
19
+ exports.buildMoveGroupParams = buildMoveGroupParams;
20
+ exports.buildMergeWindowParams = buildMergeWindowParams;
21
+ exports.buildArchiveParams = buildArchiveParams;
22
+ exports.buildCloseParams = buildCloseParams;
23
+ exports.buildReportParams = buildReportParams;
24
+ exports.buildScreenshotParams = buildScreenshotParams;
25
+ exports.buildHistoryParams = buildHistoryParams;
26
+ exports.buildUndoParams = buildUndoParams;
27
+ const fs_1 = __importDefault(require("fs"));
28
+ const output_1 = require("../output");
29
+ const args_1 = require("../args");
30
+ // ============================================================================
31
+ // Analyze Command Parameters
32
+ // ============================================================================
33
+ function buildAnalyzeParams(options) {
34
+ if (options.ungrouped && options["group-id"]) {
35
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
36
+ }
37
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
38
+ return {
39
+ staleDays: options["stale-days"] ? Number(options["stale-days"]) : undefined,
40
+ checkGitHub: Boolean(options.github),
41
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
42
+ groupTitle: options.group,
43
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
44
+ windowId: windowValue,
45
+ all: options.all === true,
46
+ githubConcurrency: options["github-concurrency"] ? Number(options["github-concurrency"]) : undefined,
47
+ githubTimeoutMs: options["github-timeout-ms"] ? Number(options["github-timeout-ms"]) : undefined,
48
+ progress: Boolean(options.progress),
49
+ };
50
+ }
51
+ // ============================================================================
52
+ // Inspect Command Parameters
53
+ // ============================================================================
54
+ function buildInspectParams(options) {
55
+ if (options.ungrouped && options["group-id"]) {
56
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
57
+ }
58
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
59
+ const selectorAttr = options["selector-attr"] ? String(options["selector-attr"]).trim() : "";
60
+ const allowedSelectorAttrs = new Set(["text", "href", "src", "href-url", "src-url"]);
61
+ if (options["selector-attr"] && (!selectorAttr || !allowedSelectorAttrs.has(selectorAttr))) {
62
+ (0, output_1.errorOut)("Invalid --selector-attr value (use text|href|src|href-url|src-url)");
63
+ }
64
+ let selectorSpecs;
65
+ if (options.selector) {
66
+ selectorSpecs = options.selector.map((value) => {
67
+ const trimmed = value.trim();
68
+ if (trimmed.startsWith("{")) {
69
+ try {
70
+ const parsed = JSON.parse(trimmed);
71
+ if (parsed && typeof parsed.selector === "string" && parsed.selector.includes(":contains(")) {
72
+ (0, output_1.errorOut)("Selector :contains() is not supported; use text filters instead.");
73
+ }
74
+ if (selectorAttr && parsed && typeof parsed === "object" && !Object.prototype.hasOwnProperty.call(parsed, "attr")) {
75
+ return { ...parsed, attr: selectorAttr };
76
+ }
77
+ return parsed;
78
+ }
79
+ catch {
80
+ (0, output_1.errorOut)(`Invalid selector JSON: ${trimmed}`);
81
+ }
82
+ }
83
+ if (trimmed.includes(":contains(")) {
84
+ (0, output_1.errorOut)("Selector :contains() is not supported; use text filters instead.");
85
+ }
86
+ if (trimmed.includes("=")) {
87
+ const [name, selector] = trimmed.split(/=(.+)/);
88
+ return selectorAttr ? { name, selector, attr: selectorAttr } : { name, selector };
89
+ }
90
+ return selectorAttr ? { selector: trimmed, attr: selectorAttr } : { selector: trimmed };
91
+ }).filter(Boolean);
92
+ }
93
+ let signalConfig;
94
+ if (options["signal-config"]) {
95
+ try {
96
+ const configRaw = fs_1.default.readFileSync(String(options["signal-config"]), "utf8");
97
+ signalConfig = JSON.parse(configRaw);
98
+ }
99
+ catch {
100
+ (0, output_1.errorOut)("Failed to read --signal-config file");
101
+ }
102
+ }
103
+ return {
104
+ all: Boolean(options.all),
105
+ windowId: windowValue,
106
+ groupTitle: options.group,
107
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
108
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
109
+ signals: options.signal ? options.signal : undefined,
110
+ selectorSpecs,
111
+ signalConfig,
112
+ signalConcurrency: options["signal-concurrency"] ? Number(options["signal-concurrency"]) : undefined,
113
+ signalTimeoutMs: options["signal-timeout-ms"] ? Number(options["signal-timeout-ms"]) : undefined,
114
+ waitFor: parseWaitFor(options["wait-for"]),
115
+ waitTimeoutMs: parseWaitTimeout(options["wait-timeout-ms"]),
116
+ progress: Boolean(options.progress),
117
+ };
118
+ }
119
+ // ============================================================================
120
+ // Focus/Refresh Command Parameters
121
+ // ============================================================================
122
+ function buildFocusParams(options) {
123
+ return {
124
+ tabId: options.tab ? Number(options.tab[0]) : undefined,
125
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
126
+ };
127
+ }
128
+ function buildRefreshParams(options) {
129
+ return {
130
+ tabId: options.tab ? Number(options.tab[0]) : undefined,
131
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
132
+ };
133
+ }
134
+ // ============================================================================
135
+ // Open Command Parameters
136
+ // ============================================================================
137
+ function buildOpenParams(options) {
138
+ const windowValue = parseWindowScope(options.window, { allowNew: true });
139
+ const openNewWindow = options["new-window"] === true || windowValue === "new";
140
+ if (options["before-tab"] != null && !Number.isFinite(Number(options["before-tab"]))) {
141
+ (0, output_1.errorOut)("Invalid --before-tab value");
142
+ }
143
+ if (options["after-tab"] != null && !Number.isFinite(Number(options["after-tab"]))) {
144
+ (0, output_1.errorOut)("Invalid --after-tab value");
145
+ }
146
+ if (options["before-tab"] != null && options["after-tab"] != null) {
147
+ (0, output_1.errorOut)("Only one target position is allowed");
148
+ }
149
+ return {
150
+ urls: options.url ? options.url.map(String) : undefined,
151
+ groupTitle: options.group,
152
+ color: (0, args_1.normalizeGroupColor)(options.color),
153
+ afterGroupTitle: options["after-group"],
154
+ beforeTabId: options["before-tab"] ? Number(options["before-tab"]) : undefined,
155
+ afterTabId: options["after-tab"] ? Number(options["after-tab"]) : undefined,
156
+ windowId: windowValue === "new" ? undefined : windowValue,
157
+ newWindow: openNewWindow,
158
+ windowGroupTitle: options["window-group"],
159
+ windowTabId: options["window-tab"] ? Number(options["window-tab"]) : undefined,
160
+ windowUrl: options["window-url"],
161
+ };
162
+ }
163
+ // ============================================================================
164
+ // Group Command Parameters
165
+ // ============================================================================
166
+ function buildGroupUpdateParams(options) {
167
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
168
+ return {
169
+ groupTitle: options.group,
170
+ groupId: options["group-id"] ? Number(options["group-id"]) : undefined,
171
+ windowId: windowValue,
172
+ title: options.title,
173
+ color: options.color,
174
+ collapsed: options.collapsed === true ? true : options.expanded === true ? false : undefined,
175
+ };
176
+ }
177
+ function buildGroupUngroupParams(options) {
178
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
179
+ return {
180
+ groupTitle: options.group,
181
+ groupId: options["group-id"] ? Number(options["group-id"]) : undefined,
182
+ windowId: windowValue,
183
+ };
184
+ }
185
+ function buildGroupAssignParams(options) {
186
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
187
+ return {
188
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
189
+ groupTitle: options.group,
190
+ groupId: options["group-id"] ? Number(options["group-id"]) : undefined,
191
+ windowId: windowValue,
192
+ create: Boolean(options.create),
193
+ color: options.color,
194
+ collapsed: options.collapsed === true ? true : options.expanded === true ? false : undefined,
195
+ };
196
+ }
197
+ // ============================================================================
198
+ // Move Command Parameters
199
+ // ============================================================================
200
+ function buildMoveTabParams(options) {
201
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
202
+ return {
203
+ tabId: options.tab ? Number(options.tab[0]) : undefined,
204
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
205
+ beforeTabId: options["before-tab"] ? Number(options["before-tab"]) : undefined,
206
+ afterTabId: options["after-tab"] ? Number(options["after-tab"]) : undefined,
207
+ beforeGroupTitle: options["before-group"],
208
+ afterGroupTitle: options["after-group"],
209
+ windowId: windowValue,
210
+ newWindow: options["new-window"] === true,
211
+ };
212
+ }
213
+ function buildMoveGroupParams(options) {
214
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
215
+ return {
216
+ groupTitle: options.group,
217
+ groupId: options["group-id"] ? Number(options["group-id"]) : undefined,
218
+ beforeTabId: options["before-tab"] ? Number(options["before-tab"]) : undefined,
219
+ afterTabId: options["after-tab"] ? Number(options["after-tab"]) : undefined,
220
+ beforeGroupTitle: options["before-group"],
221
+ afterGroupTitle: options["after-group"],
222
+ windowId: windowValue,
223
+ newWindow: options["new-window"] === true,
224
+ };
225
+ }
226
+ function buildMergeWindowParams(options) {
227
+ return {
228
+ fromWindowId: options.from ? Number(options.from) : undefined,
229
+ toWindowId: options.to ? Number(options.to) : undefined,
230
+ windowId: options.from ? Number(options.from) : undefined,
231
+ closeSource: options["close-source"] === true,
232
+ confirmed: options.confirm === true,
233
+ };
234
+ }
235
+ // ============================================================================
236
+ // Archive/Close Command Parameters
237
+ // ============================================================================
238
+ function buildArchiveParams(options) {
239
+ if (options.ungrouped && options["group-id"]) {
240
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
241
+ }
242
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
243
+ return {
244
+ all: Boolean(options.all),
245
+ windowId: windowValue,
246
+ groupTitle: options.group,
247
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
248
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
249
+ };
250
+ }
251
+ function buildCloseParams(options) {
252
+ if (options.ungrouped && options["group-id"]) {
253
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
254
+ }
255
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
256
+ if (options.apply) {
257
+ return { mode: "apply", analysisId: options.apply };
258
+ }
259
+ if (!options.confirm) {
260
+ (0, output_1.errorOut)("Direct close requires --confirm");
261
+ }
262
+ return {
263
+ mode: "direct",
264
+ confirmed: true,
265
+ windowId: windowValue,
266
+ groupTitle: options.group,
267
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
268
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
269
+ };
270
+ }
271
+ // ============================================================================
272
+ // Report Command Parameters
273
+ // ============================================================================
274
+ function buildReportParams(options) {
275
+ if (options.ungrouped && options["group-id"]) {
276
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
277
+ }
278
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
279
+ return {
280
+ all: Boolean(options.all),
281
+ windowId: windowValue,
282
+ groupTitle: options.group,
283
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
284
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
285
+ };
286
+ }
287
+ // ============================================================================
288
+ // Screenshot Command Parameters
289
+ // ============================================================================
290
+ function buildScreenshotParams(options) {
291
+ if (options.ungrouped && options["group-id"]) {
292
+ (0, output_1.errorOut)("--ungrouped cannot be combined with --group-id");
293
+ }
294
+ const windowValue = parseWindowScope(options.window, { allowNew: false });
295
+ const outDir = options.out != null ? String(options.out).trim() : "";
296
+ if (options.out && !outDir) {
297
+ (0, output_1.errorOut)("--out requires a directory path");
298
+ }
299
+ return {
300
+ all: Boolean(options.all),
301
+ windowId: windowValue,
302
+ groupTitle: options.group,
303
+ groupId: options.ungrouped ? -1 : (options["group-id"] ? Number(options["group-id"]) : undefined),
304
+ tabIds: options.tab ? options.tab.map(Number) : undefined,
305
+ mode: options.mode,
306
+ format: options.format,
307
+ quality: options.quality != null ? Number(options.quality) : undefined,
308
+ tileMaxDim: options["tile-max-dim"] != null ? Number(options["tile-max-dim"]) : undefined,
309
+ maxBytes: options["max-bytes"] != null ? Number(options["max-bytes"]) : undefined,
310
+ waitFor: parseWaitFor(options["wait-for"]),
311
+ waitTimeoutMs: parseWaitTimeout(options["wait-timeout-ms"]),
312
+ outDir: outDir || undefined,
313
+ progress: Boolean(options.progress),
314
+ };
315
+ }
316
+ const VALID_WAIT_FOR = ["load", "dom", "settle", "none"];
317
+ function parseWaitFor(value) {
318
+ if (value == null) {
319
+ return undefined;
320
+ }
321
+ const normalized = String(value).trim().toLowerCase();
322
+ if (!normalized) {
323
+ return undefined;
324
+ }
325
+ if (!VALID_WAIT_FOR.includes(normalized)) {
326
+ (0, output_1.errorOut)(`Invalid --wait-for value (use ${VALID_WAIT_FOR.join("|")})`);
327
+ }
328
+ return normalized;
329
+ }
330
+ function parseWaitTimeout(value) {
331
+ if (value == null) {
332
+ return undefined;
333
+ }
334
+ const parsed = Number(value);
335
+ if (!Number.isFinite(parsed) || parsed <= 0) {
336
+ (0, output_1.errorOut)("Invalid --wait-timeout-ms value");
337
+ }
338
+ return Math.floor(parsed);
339
+ }
340
+ function parseWindowScope(value, { allowNew }) {
341
+ if (value == null) {
342
+ return undefined;
343
+ }
344
+ if (typeof value === "string") {
345
+ const trimmed = value.trim().toLowerCase();
346
+ if (!trimmed) {
347
+ return undefined;
348
+ }
349
+ if (trimmed === "active" || trimmed === "last-focused") {
350
+ return trimmed;
351
+ }
352
+ if (trimmed === "new") {
353
+ if (!allowNew) {
354
+ (0, output_1.errorOut)("--window new is only supported by open");
355
+ }
356
+ return trimmed;
357
+ }
358
+ }
359
+ const numeric = Number(value);
360
+ if (!Number.isFinite(numeric)) {
361
+ (0, output_1.errorOut)("Invalid --window value");
362
+ }
363
+ return numeric;
364
+ }
365
+ // ============================================================================
366
+ // History/Undo Command Parameters
367
+ // ============================================================================
368
+ function buildHistoryParams(options) {
369
+ return {
370
+ limit: options.limit ? Number(options.limit) : undefined,
371
+ };
372
+ }
373
+ function buildUndoParams(options) {
374
+ return {
375
+ txid: options._[0] || options.txid,
376
+ latest: options.latest === true,
377
+ };
378
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runProfileList = runProfileList;
4
+ exports.runProfileShow = runProfileShow;
5
+ exports.runProfileSwitch = runProfileSwitch;
6
+ exports.runProfileRemove = runProfileRemove;
7
+ const constants_1 = require("../constants");
8
+ const output_1 = require("../output");
9
+ const profiles_1 = require("../../../shared/profiles");
10
+ function runProfileList(options, prettyOutput) {
11
+ const profiles = (0, profiles_1.listProfiles)();
12
+ (0, output_1.printJson)({
13
+ ok: true,
14
+ action: "profile-list",
15
+ data: {
16
+ profiles: profiles.map(({ name, profile, isDefault }) => ({
17
+ name,
18
+ browser: profile.browser,
19
+ dataDir: profile.dataDir,
20
+ isDefault,
21
+ })),
22
+ },
23
+ }, prettyOutput);
24
+ }
25
+ function runProfileShow(options, prettyOutput) {
26
+ const config = (0, constants_1.resolveConfig)();
27
+ const active = (0, profiles_1.getActiveProfile)();
28
+ if (!active) {
29
+ (0, output_1.printJson)({
30
+ ok: true,
31
+ action: "profile-show",
32
+ data: {
33
+ mode: "legacy",
34
+ message: "No profiles configured. Run tabctl setup to create one.",
35
+ dataDir: config.dataDir,
36
+ socketPath: config.socketPath,
37
+ policyPath: config.policyPath,
38
+ },
39
+ }, prettyOutput);
40
+ return;
41
+ }
42
+ const registry = (0, profiles_1.loadProfiles)();
43
+ (0, output_1.printJson)({
44
+ ok: true,
45
+ action: "profile-show",
46
+ data: {
47
+ name: active.name,
48
+ browser: active.profile.browser,
49
+ extensionId: active.profile.extensionId,
50
+ dataDir: active.profile.dataDir,
51
+ ...(active.profile.userDataDir ? { userDataDir: active.profile.userDataDir } : {}),
52
+ socketPath: config.socketPath,
53
+ policyPath: config.policyPath,
54
+ isDefault: active.name === registry.default,
55
+ },
56
+ }, prettyOutput);
57
+ }
58
+ function runProfileSwitch(options, prettyOutput) {
59
+ const name = options._[0];
60
+ if (!name) {
61
+ (0, output_1.errorOut)("Usage: tabctl profile-switch <name>");
62
+ }
63
+ const registry = (0, profiles_1.loadProfiles)();
64
+ if (!(name in registry.profiles)) {
65
+ (0, output_1.errorOut)(`Profile "${name}" not found`);
66
+ }
67
+ registry.default = name;
68
+ (0, profiles_1.saveProfiles)(registry);
69
+ (0, output_1.printJson)({
70
+ ok: true,
71
+ action: "profile-switch",
72
+ data: { name, message: `Default profile set to "${name}"` },
73
+ }, prettyOutput);
74
+ }
75
+ function runProfileRemove(options, prettyOutput) {
76
+ const name = options._[0];
77
+ if (!name) {
78
+ (0, output_1.errorOut)("Usage: tabctl profile-remove <name>");
79
+ }
80
+ try {
81
+ const registry = (0, profiles_1.removeProfile)(name);
82
+ (0, output_1.printJson)({
83
+ ok: true,
84
+ action: "profile-remove",
85
+ data: { name, newDefault: registry.default, message: `Profile "${name}" removed` },
86
+ }, prettyOutput);
87
+ }
88
+ catch (err) {
89
+ (0, output_1.errorOut)(err.message);
90
+ }
91
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUPPORTED_SIGNAL_SET = exports.SUPPORTED_SIGNALS = exports.SKILL_REPO = exports.SKILL_NAME = exports.DEFAULT_PAGE_LIMIT = exports.GROUP_COLORS = exports.EXTENSION_ID_PATTERN = exports.HOST_DESCRIPTION = exports.HOST_NAME = exports.resolveConfig = exports.DIRTY = exports.GIT_SHA = exports.BASE_VERSION = exports.VERSION = void 0;
4
+ // Re-export version info from shared module
5
+ var version_1 = require("../../shared/version");
6
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
7
+ Object.defineProperty(exports, "BASE_VERSION", { enumerable: true, get: function () { return version_1.BASE_VERSION; } });
8
+ Object.defineProperty(exports, "GIT_SHA", { enumerable: true, get: function () { return version_1.GIT_SHA; } });
9
+ Object.defineProperty(exports, "DIRTY", { enumerable: true, get: function () { return version_1.DIRTY; } });
10
+ var config_1 = require("../../shared/config");
11
+ Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return config_1.resolveConfig; } });
12
+ exports.HOST_NAME = "com.erwinkroon.tabctl";
13
+ exports.HOST_DESCRIPTION = "tabctl native host";
14
+ exports.EXTENSION_ID_PATTERN = /^[a-p]{32}$/;
15
+ exports.GROUP_COLORS = new Set([
16
+ "grey",
17
+ "blue",
18
+ "red",
19
+ "yellow",
20
+ "green",
21
+ "pink",
22
+ "purple",
23
+ "cyan",
24
+ "orange",
25
+ ]);
26
+ exports.DEFAULT_PAGE_LIMIT = 100;
27
+ exports.SKILL_NAME = "tabctl";
28
+ exports.SKILL_REPO = process.env.TABCTL_SKILL_REPO || "https://github.com/ekroon/tabctl";
29
+ exports.SUPPORTED_SIGNALS = ["page-meta", "github-state", "selector"];
30
+ exports.SUPPORTED_SIGNAL_SET = new Set(exports.SUPPORTED_SIGNALS);