tabctl 0.1.4 → 0.2.1
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.
- package/{cli → dist/cli}/lib/commands/index.js +4 -2
- package/dist/cli/lib/commands/meta.js +226 -0
- package/dist/cli/lib/commands/params-groups.js +40 -0
- package/dist/cli/lib/commands/params-move.js +44 -0
- package/{cli → dist/cli}/lib/commands/params.js +61 -125
- package/{cli/lib/commands/meta.js → dist/cli/lib/commands/setup.js} +26 -222
- package/{cli/lib/options.js → dist/cli/lib/options-commands.js} +3 -155
- package/dist/cli/lib/options-groups.js +41 -0
- package/dist/cli/lib/options.js +125 -0
- package/{cli → dist/cli}/lib/output.js +5 -4
- package/dist/cli/lib/policy-filter.js +202 -0
- package/dist/cli/lib/response.js +235 -0
- package/{cli → dist/cli}/lib/scope.js +3 -31
- package/dist/cli/tabctl.js +463 -0
- package/dist/extension/background.js +3398 -0
- package/dist/extension/lib/archive.js +444 -0
- package/dist/extension/lib/content.js +320 -0
- package/dist/extension/lib/deps.js +4 -0
- package/dist/extension/lib/groups.js +443 -0
- package/dist/extension/lib/inspect.js +316 -0
- package/dist/extension/lib/move.js +342 -0
- package/dist/extension/lib/screenshot.js +367 -0
- package/dist/extension/lib/tabs.js +395 -0
- package/dist/extension/lib/undo-handlers.js +439 -0
- package/{extension → dist/extension}/manifest.json +2 -2
- package/dist/host/host.js +124 -0
- package/{host/host.js → dist/host/lib/handlers.js} +84 -187
- package/{shared → dist/shared}/version.js +2 -2
- package/package.json +12 -10
- package/cli/tabctl.js +0 -841
- package/extension/background.js +0 -3372
- package/extension/manifest.template.json +0 -22
- /package/{cli → dist/cli}/lib/args.js +0 -0
- /package/{cli → dist/cli}/lib/client.js +0 -0
- /package/{cli → dist/cli}/lib/commands/list.js +0 -0
- /package/{cli → dist/cli}/lib/commands/profile.js +0 -0
- /package/{cli → dist/cli}/lib/constants.js +0 -0
- /package/{cli → dist/cli}/lib/help.js +0 -0
- /package/{cli → dist/cli}/lib/pagination.js +0 -0
- /package/{cli → dist/cli}/lib/policy.js +0 -0
- /package/{cli → dist/cli}/lib/report.js +0 -0
- /package/{cli → dist/cli}/lib/snapshot.js +0 -0
- /package/{cli → dist/cli}/lib/types.js +0 -0
- /package/{host → dist/host}/host.sh +0 -0
- /package/{host → dist/host}/lib/undo.js +0 -0
- /package/{shared → dist/shared}/config.js +0 -0
- /package/{shared → dist/shared}/extension-sync.js +0 -0
- /package/{shared → dist/shared}/profiles.js +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Setup command handler: interactive browser profile configuration.
|
|
4
|
+
* Extracted from meta.ts for modularity.
|
|
5
5
|
*/
|
|
6
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resolveBrowser = resolveBrowser;
|
|
11
|
+
exports.resolveExtensionId = resolveExtensionId;
|
|
12
|
+
exports.promptExtensionId = promptExtensionId;
|
|
13
|
+
exports.resolveNodePath = resolveNodePath;
|
|
14
|
+
exports.resolveManifestDir = resolveManifestDir;
|
|
15
|
+
exports.writeWrapper = writeWrapper;
|
|
10
16
|
exports.runSetup = runSetup;
|
|
11
|
-
exports.runSkillInstall = runSkillInstall;
|
|
12
|
-
exports.runVersion = runVersion;
|
|
13
|
-
exports.runPolicy = runPolicy;
|
|
14
|
-
exports.runHistory = runHistory;
|
|
15
|
-
exports.runUndo = runUndo;
|
|
16
|
-
exports.runPing = runPing;
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
18
|
const os_1 = __importDefault(require("os"));
|
|
19
19
|
const path_1 = __importDefault(require("path"));
|
|
@@ -21,13 +21,9 @@ const readline_1 = __importDefault(require("readline"));
|
|
|
21
21
|
const node_child_process_1 = require("node:child_process");
|
|
22
22
|
const constants_1 = require("../constants");
|
|
23
23
|
const output_1 = require("../output");
|
|
24
|
-
const client_1 = require("../client");
|
|
25
|
-
const policy_1 = require("../policy");
|
|
26
24
|
const profiles_1 = require("../../../shared/profiles");
|
|
25
|
+
const config_1 = require("../../../shared/config");
|
|
27
26
|
const extension_sync_1 = require("../../../shared/extension-sync");
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Setup Command
|
|
30
|
-
// ============================================================================
|
|
31
27
|
function resolveBrowser(value) {
|
|
32
28
|
if (typeof value !== "string") {
|
|
33
29
|
return null;
|
|
@@ -263,6 +259,9 @@ async function runSetup(options, prettyOutput) {
|
|
|
263
259
|
profileEntry.userDataDir = userDataDir;
|
|
264
260
|
}
|
|
265
261
|
const registry = (0, profiles_1.addProfile)(profileName, profileEntry);
|
|
262
|
+
// Ensure printJson footer reflects the newly-created profile
|
|
263
|
+
(0, config_1.resetConfig)();
|
|
264
|
+
process.env.TABCTL_PROFILE = profileName;
|
|
266
265
|
(0, output_1.printJson)({
|
|
267
266
|
ok: true,
|
|
268
267
|
action: "setup",
|
|
@@ -281,213 +280,18 @@ async function runSetup(options, prettyOutput) {
|
|
|
281
280
|
extensionSynced: extensionSync?.synced || false,
|
|
282
281
|
},
|
|
283
282
|
}, prettyOutput);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
function resolveProjectRoot() {
|
|
300
|
-
try {
|
|
301
|
-
return fs_1.default.realpathSync(process.cwd());
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return path_1.default.resolve(process.cwd());
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function resolveConfigHome() {
|
|
308
|
-
return process.env.XDG_CONFIG_HOME || path_1.default.join(os_1.default.homedir(), ".config");
|
|
309
|
-
}
|
|
310
|
-
function resolveSkillTargetDir(globalInstall) {
|
|
311
|
-
if (globalInstall) {
|
|
312
|
-
return path_1.default.join(resolveConfigHome(), "opencode", "skills", constants_1.SKILL_NAME);
|
|
313
|
-
}
|
|
314
|
-
return path_1.default.join(resolveProjectRoot(), ".opencode", "skills", constants_1.SKILL_NAME);
|
|
315
|
-
}
|
|
316
|
-
function runSkillsCli(args) {
|
|
317
|
-
const result = (0, node_child_process_1.spawnSync)("npx", ["skills", ...args], { stdio: "pipe" });
|
|
318
|
-
if (result.error) {
|
|
319
|
-
(0, output_1.errorOut)(`Failed to run skills CLI: ${result.error.message}`);
|
|
320
|
-
}
|
|
321
|
-
if (typeof result.status === "number" && result.status !== 0) {
|
|
322
|
-
const stderr = result.stderr ? result.stderr.toString().trim() : "";
|
|
323
|
-
const stdout = result.stdout ? result.stdout.toString().trim() : "";
|
|
324
|
-
const detail = stderr || stdout;
|
|
325
|
-
const message = detail ? `skills CLI failed: ${detail}` : `skills CLI exited with status ${result.status}`;
|
|
326
|
-
(0, output_1.errorOut)(message);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function runSkillInstall(options, prettyOutput) {
|
|
330
|
-
const globalInstall = options.global === true;
|
|
331
|
-
const installTarget = resolveSkillTargetDir(globalInstall);
|
|
332
|
-
const agents = Array.isArray(options.agent)
|
|
333
|
-
? options.agent.filter((value) => typeof value === "string" && value.trim())
|
|
334
|
-
: [];
|
|
335
|
-
const args = ["add", constants_1.SKILL_REPO, "--skill", constants_1.SKILL_NAME];
|
|
336
|
-
if (agents.length > 0) {
|
|
337
|
-
for (const agent of agents) {
|
|
338
|
-
args.push("-a", agent);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (globalInstall) {
|
|
342
|
-
args.push("-g");
|
|
343
|
-
}
|
|
344
|
-
const hintAgents = agents.length > 0 ? agents.map((agent) => `-a ${formatCliArgValue(agent)}`).join(" ") : "";
|
|
345
|
-
const installHintParts = ["npx skills add", formatCliArgValue(constants_1.SKILL_REPO), "--skill", constants_1.SKILL_NAME];
|
|
346
|
-
if (hintAgents) {
|
|
347
|
-
installHintParts.push(hintAgents);
|
|
348
|
-
}
|
|
349
|
-
if (globalInstall) {
|
|
350
|
-
installHintParts.push("-g");
|
|
351
|
-
}
|
|
352
|
-
const installHint = installHintParts.join(" ").trim();
|
|
353
|
-
runSkillsCli(args);
|
|
354
|
-
(0, output_1.printJson)({
|
|
355
|
-
ok: true,
|
|
356
|
-
data: {
|
|
357
|
-
name: constants_1.SKILL_NAME,
|
|
358
|
-
targetDir: installTarget,
|
|
359
|
-
scope: globalInstall ? "global" : "project",
|
|
360
|
-
installHint,
|
|
361
|
-
tool: "skills",
|
|
362
|
-
},
|
|
363
|
-
}, prettyOutput);
|
|
364
|
-
}
|
|
365
|
-
// ============================================================================
|
|
366
|
-
// Version Command
|
|
367
|
-
// ============================================================================
|
|
368
|
-
function runVersion(prettyOutput) {
|
|
369
|
-
(0, output_1.printJson)({
|
|
370
|
-
ok: true,
|
|
371
|
-
data: {
|
|
372
|
-
version: constants_1.VERSION,
|
|
373
|
-
baseVersion: constants_1.BASE_VERSION,
|
|
374
|
-
gitSha: constants_1.GIT_SHA,
|
|
375
|
-
dirty: constants_1.DIRTY,
|
|
376
|
-
component: "cli",
|
|
377
|
-
},
|
|
378
|
-
}, prettyOutput);
|
|
379
|
-
}
|
|
380
|
-
// ============================================================================
|
|
381
|
-
// Policy Command
|
|
382
|
-
// ============================================================================
|
|
383
|
-
function runPolicy(options, policyContext, prettyOutput) {
|
|
384
|
-
const policyPath = (0, policy_1.defaultPolicyPath)();
|
|
385
|
-
if (options.init) {
|
|
386
|
-
if (fs_1.default.existsSync(policyPath)) {
|
|
387
|
-
(0, output_1.printJson)({
|
|
388
|
-
ok: true,
|
|
389
|
-
data: {
|
|
390
|
-
status: "exists",
|
|
391
|
-
path: policyPath,
|
|
392
|
-
},
|
|
393
|
-
}, prettyOutput);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
const dir = path_1.default.dirname(policyPath);
|
|
397
|
-
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
398
|
-
fs_1.default.writeFileSync(policyPath, JSON.stringify((0, policy_1.defaultPolicyTemplate)(), null, 2), "utf8");
|
|
399
|
-
(0, output_1.printJson)({
|
|
400
|
-
ok: true,
|
|
401
|
-
data: {
|
|
402
|
-
status: "created",
|
|
403
|
-
path: policyPath,
|
|
404
|
-
},
|
|
405
|
-
}, prettyOutput);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
const policySummary = (0, policy_1.summarizePolicy)(policyContext.policy, policyContext.path);
|
|
409
|
-
(0, output_1.printJson)({
|
|
410
|
-
ok: true,
|
|
411
|
-
data: {
|
|
412
|
-
...policySummary,
|
|
413
|
-
path: policyPath,
|
|
414
|
-
},
|
|
415
|
-
}, prettyOutput);
|
|
416
|
-
}
|
|
417
|
-
// ============================================================================
|
|
418
|
-
// History Command
|
|
419
|
-
// ============================================================================
|
|
420
|
-
async function runHistory(options, prettyOutput) {
|
|
421
|
-
const params = {
|
|
422
|
-
limit: options.limit ? Number(options.limit) : undefined,
|
|
423
|
-
};
|
|
424
|
-
const response = await (0, client_1.sendRequest)({
|
|
425
|
-
id: (0, client_1.createRequestId)(),
|
|
426
|
-
action: "history",
|
|
427
|
-
params,
|
|
428
|
-
client: {
|
|
429
|
-
component: "cli",
|
|
430
|
-
version: constants_1.VERSION,
|
|
431
|
-
baseVersion: constants_1.BASE_VERSION,
|
|
432
|
-
gitSha: constants_1.GIT_SHA,
|
|
433
|
-
dirty: constants_1.DIRTY,
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
(0, output_1.printJson)(response, prettyOutput);
|
|
437
|
-
if (!response.ok) {
|
|
438
|
-
process.exit(1);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
// ============================================================================
|
|
442
|
-
// Undo Command
|
|
443
|
-
// ============================================================================
|
|
444
|
-
async function runUndo(options, prettyOutput) {
|
|
445
|
-
const positionalTxid = options._[0];
|
|
446
|
-
const flagTxid = options.txid;
|
|
447
|
-
const useLatest = options.latest === true;
|
|
448
|
-
if (useLatest && (positionalTxid || flagTxid)) {
|
|
449
|
-
(0, output_1.errorOut)("--latest cannot be combined with a txid argument or --txid");
|
|
450
|
-
}
|
|
451
|
-
const txid = positionalTxid || flagTxid;
|
|
452
|
-
const params = {
|
|
453
|
-
txid,
|
|
454
|
-
latest: useLatest,
|
|
455
|
-
};
|
|
456
|
-
const response = await (0, client_1.sendRequest)({
|
|
457
|
-
id: (0, client_1.createRequestId)(),
|
|
458
|
-
action: "undo",
|
|
459
|
-
params,
|
|
460
|
-
client: {
|
|
461
|
-
component: "cli",
|
|
462
|
-
version: constants_1.VERSION,
|
|
463
|
-
baseVersion: constants_1.BASE_VERSION,
|
|
464
|
-
gitSha: constants_1.GIT_SHA,
|
|
465
|
-
dirty: constants_1.DIRTY,
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
(0, output_1.printJson)(response, prettyOutput);
|
|
469
|
-
if (!response.ok) {
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
// ============================================================================
|
|
474
|
-
// Ping Command
|
|
475
|
-
// ============================================================================
|
|
476
|
-
async function runPing(prettyOutput) {
|
|
477
|
-
const response = await (0, client_1.sendRequest)({
|
|
478
|
-
id: (0, client_1.createRequestId)(),
|
|
479
|
-
action: "ping",
|
|
480
|
-
params: {},
|
|
481
|
-
client: {
|
|
482
|
-
component: "cli",
|
|
483
|
-
version: constants_1.VERSION,
|
|
484
|
-
baseVersion: constants_1.BASE_VERSION,
|
|
485
|
-
gitSha: constants_1.GIT_SHA,
|
|
486
|
-
dirty: constants_1.DIRTY,
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
(0, output_1.printJson)(response, prettyOutput);
|
|
490
|
-
if (!response.ok) {
|
|
491
|
-
process.exit(1);
|
|
492
|
-
}
|
|
283
|
+
if (registry.default !== profileName) {
|
|
284
|
+
process.stderr.write([
|
|
285
|
+
"",
|
|
286
|
+
`Profile "${profileName}" created (current default: "${registry.default}").`,
|
|
287
|
+
` To use: tabctl --profile ${profileName} <command>`,
|
|
288
|
+
` To make default: tabctl profile-switch ${profileName}`,
|
|
289
|
+
"",
|
|
290
|
+
].join("\n"));
|
|
291
|
+
}
|
|
292
|
+
process.stderr.write([
|
|
293
|
+
`Verify connection: tabctl --profile ${profileName} ping`,
|
|
294
|
+
`If ping fails, ensure the ${browser === "edge" ? "Edge" : "Chrome"} extension is active.`,
|
|
295
|
+
"",
|
|
296
|
+
].join("\n"));
|
|
493
297
|
}
|
|
@@ -1,61 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Central source of truth for CLI options and command metadata.
|
|
4
|
-
* This eliminates duplication between argument parsing and help display.
|
|
5
|
-
*/
|
|
6
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.COMMANDS =
|
|
8
|
-
|
|
9
|
-
exports.getAllowedFlags = getAllowedFlags;
|
|
10
|
-
exports.getCommandAllowedFlags = getCommandAllowedFlags;
|
|
11
|
-
exports.getCommandGroups = getCommandGroups;
|
|
12
|
-
exports.getCommandOptions = getCommandOptions;
|
|
13
|
-
exports.commandSupportsGroup = commandSupportsGroup;
|
|
14
|
-
const GLOBAL_FLAGS = ["help", "json", "pretty"];
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Option Group Definitions
|
|
17
|
-
// ============================================================================
|
|
18
|
-
exports.OPTION_GROUPS = {
|
|
19
|
-
scope: {
|
|
20
|
-
name: "Scope Options",
|
|
21
|
-
description: "Filter which tabs/groups to operate on",
|
|
22
|
-
options: [
|
|
23
|
-
{ flag: "--tab <id>", desc: "Target specific tab(s) by ID", repeatable: true },
|
|
24
|
-
{ flag: "--group <name>", desc: "Target tabs in group by title" },
|
|
25
|
-
{ flag: "--group-id <id>", desc: "Target group by ID (use -1 for ungrouped)" },
|
|
26
|
-
{ flag: "--ungrouped", desc: "Alias for --group-id -1" },
|
|
27
|
-
{ flag: "--window <id|active|last-focused>", desc: "Target tabs in specific window" },
|
|
28
|
-
{ flag: "--all", desc: "Target all eligible tabs" },
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
pagination: {
|
|
32
|
-
name: "Pagination Options",
|
|
33
|
-
description: "Control result paging (default limit: 100)",
|
|
34
|
-
options: [
|
|
35
|
-
{ flag: "--limit <n>", desc: "Maximum items to return" },
|
|
36
|
-
{ flag: "--offset <n>", desc: "Skip first n items" },
|
|
37
|
-
{ flag: "--no-page", desc: "Disable pagination, return all results" },
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
exports.SCREENSHOT_OPTIONS = {
|
|
42
|
-
name: "Screenshot Options",
|
|
43
|
-
description: "Control screenshot capture",
|
|
44
|
-
options: [
|
|
45
|
-
{ flag: "--mode viewport|full", desc: "Capture mode" },
|
|
46
|
-
{ flag: "--format png|jpeg", desc: "Image format" },
|
|
47
|
-
{ flag: "--quality <n>", desc: "JPEG quality (0-100)" },
|
|
48
|
-
{ flag: "--tile-max-dim <px>", desc: "Max tile dimension in pixels" },
|
|
49
|
-
{ flag: "--max-bytes <n>", desc: "Max bytes per tile" },
|
|
50
|
-
{ flag: "--wait-for load|dom|settle|none", desc: "Wait for page readiness before capture" },
|
|
51
|
-
{ flag: "--wait-timeout-ms <ms>", desc: "Timeout for page readiness wait" },
|
|
52
|
-
{ flag: "--out <dir>", desc: "Write files to directory" },
|
|
53
|
-
{ flag: "--progress", desc: "Show progress during capture" },
|
|
54
|
-
],
|
|
55
|
-
};
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// Command Metadata
|
|
58
|
-
// ============================================================================
|
|
3
|
+
exports.COMMANDS = void 0;
|
|
4
|
+
const options_groups_1 = require("./options-groups");
|
|
59
5
|
exports.COMMANDS = {
|
|
60
6
|
help: {
|
|
61
7
|
description: "Show help information",
|
|
@@ -111,7 +57,7 @@ exports.COMMANDS = {
|
|
|
111
57
|
screenshot: {
|
|
112
58
|
description: "Capture screenshots from tabs",
|
|
113
59
|
groups: ["scope"],
|
|
114
|
-
options:
|
|
60
|
+
options: options_groups_1.SCREENSHOT_OPTIONS.options,
|
|
115
61
|
},
|
|
116
62
|
focus: {
|
|
117
63
|
description: "Focus a specific tab",
|
|
@@ -308,101 +254,3 @@ exports.COMMANDS = {
|
|
|
308
254
|
description: "Test connection to browser extension",
|
|
309
255
|
},
|
|
310
256
|
};
|
|
311
|
-
// ============================================================================
|
|
312
|
-
// Helper Functions
|
|
313
|
-
// ============================================================================
|
|
314
|
-
/**
|
|
315
|
-
* Get all allowed flags for argument parsing validation.
|
|
316
|
-
*/
|
|
317
|
-
function getBooleanFlags() {
|
|
318
|
-
const flags = new Set();
|
|
319
|
-
GLOBAL_FLAGS.forEach((flag) => flags.add(flag));
|
|
320
|
-
const addFromOptions = (options) => {
|
|
321
|
-
for (const opt of options) {
|
|
322
|
-
if (!/^--[a-z-]+$/.test(opt.flag.trim())) {
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
326
|
-
if (match) {
|
|
327
|
-
flags.add(match[1]);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
for (const group of Object.values(exports.OPTION_GROUPS)) {
|
|
332
|
-
addFromOptions(group.options);
|
|
333
|
-
}
|
|
334
|
-
for (const cmd of Object.values(exports.COMMANDS)) {
|
|
335
|
-
if (cmd.options) {
|
|
336
|
-
addFromOptions(cmd.options);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return flags;
|
|
340
|
-
}
|
|
341
|
-
function getAllowedFlags() {
|
|
342
|
-
const flags = new Set();
|
|
343
|
-
for (const flag of getBooleanFlags()) {
|
|
344
|
-
flags.add(flag);
|
|
345
|
-
}
|
|
346
|
-
// Add value flags from option groups
|
|
347
|
-
for (const group of Object.values(exports.OPTION_GROUPS)) {
|
|
348
|
-
for (const opt of group.options) {
|
|
349
|
-
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
350
|
-
if (match)
|
|
351
|
-
flags.add(match[1]);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// Add value flags from command options
|
|
355
|
-
for (const cmd of Object.values(exports.COMMANDS)) {
|
|
356
|
-
for (const opt of cmd.options || []) {
|
|
357
|
-
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
358
|
-
if (match)
|
|
359
|
-
flags.add(match[1]);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
flags.add("profile");
|
|
363
|
-
return flags;
|
|
364
|
-
}
|
|
365
|
-
function getCommandAllowedFlags(command) {
|
|
366
|
-
const flags = new Set(GLOBAL_FLAGS);
|
|
367
|
-
flags.add("profile");
|
|
368
|
-
const meta = exports.COMMANDS[command];
|
|
369
|
-
const addOptions = (options) => {
|
|
370
|
-
if (!options) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
for (const opt of options) {
|
|
374
|
-
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
375
|
-
if (match) {
|
|
376
|
-
flags.add(match[1]);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
if (meta?.groups) {
|
|
381
|
-
for (const groupKey of meta.groups) {
|
|
382
|
-
addOptions(exports.OPTION_GROUPS[groupKey]?.options);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
addOptions(meta?.options);
|
|
386
|
-
return flags;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Get the option groups for a command.
|
|
390
|
-
*/
|
|
391
|
-
function getCommandGroups(command) {
|
|
392
|
-
const meta = exports.COMMANDS[command];
|
|
393
|
-
return meta?.groups ? [...meta.groups] : [];
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Get command-specific options (not from groups).
|
|
397
|
-
*/
|
|
398
|
-
function getCommandOptions(command) {
|
|
399
|
-
const meta = exports.COMMANDS[command];
|
|
400
|
-
return meta?.options ? [...meta.options] : [];
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Check if a command supports a specific option group.
|
|
404
|
-
*/
|
|
405
|
-
function commandSupportsGroup(command, group) {
|
|
406
|
-
const meta = exports.COMMANDS[command];
|
|
407
|
-
return meta?.groups?.includes(group) ?? false;
|
|
408
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCREENSHOT_OPTIONS = exports.OPTION_GROUPS = void 0;
|
|
4
|
+
exports.OPTION_GROUPS = {
|
|
5
|
+
scope: {
|
|
6
|
+
name: "Scope Options",
|
|
7
|
+
description: "Filter which tabs/groups to operate on",
|
|
8
|
+
options: [
|
|
9
|
+
{ flag: "--tab <id>", desc: "Target specific tab(s) by ID", repeatable: true },
|
|
10
|
+
{ flag: "--group <name>", desc: "Target tabs in group by title" },
|
|
11
|
+
{ flag: "--group-id <id>", desc: "Target group by ID (use -1 for ungrouped)" },
|
|
12
|
+
{ flag: "--ungrouped", desc: "Alias for --group-id -1" },
|
|
13
|
+
{ flag: "--window <id|active|last-focused>", desc: "Target tabs in specific window" },
|
|
14
|
+
{ flag: "--all", desc: "Target all eligible tabs" },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
pagination: {
|
|
18
|
+
name: "Pagination Options",
|
|
19
|
+
description: "Control result paging (default limit: 100)",
|
|
20
|
+
options: [
|
|
21
|
+
{ flag: "--limit <n>", desc: "Maximum items to return" },
|
|
22
|
+
{ flag: "--offset <n>", desc: "Skip first n items" },
|
|
23
|
+
{ flag: "--no-page", desc: "Disable pagination, return all results" },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
exports.SCREENSHOT_OPTIONS = {
|
|
28
|
+
name: "Screenshot Options",
|
|
29
|
+
description: "Control screenshot capture",
|
|
30
|
+
options: [
|
|
31
|
+
{ flag: "--mode viewport|full", desc: "Capture mode" },
|
|
32
|
+
{ flag: "--format png|jpeg", desc: "Image format" },
|
|
33
|
+
{ flag: "--quality <n>", desc: "JPEG quality (0-100)" },
|
|
34
|
+
{ flag: "--tile-max-dim <px>", desc: "Max tile dimension in pixels" },
|
|
35
|
+
{ flag: "--max-bytes <n>", desc: "Max bytes per tile" },
|
|
36
|
+
{ flag: "--wait-for load|dom|settle|none", desc: "Wait for page readiness before capture" },
|
|
37
|
+
{ flag: "--wait-timeout-ms <ms>", desc: "Timeout for page readiness wait" },
|
|
38
|
+
{ flag: "--out <dir>", desc: "Write files to directory" },
|
|
39
|
+
{ flag: "--progress", desc: "Show progress during capture" },
|
|
40
|
+
],
|
|
41
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Central source of truth for CLI options and command metadata.
|
|
4
|
+
* This eliminates duplication between argument parsing and help display.
|
|
5
|
+
*
|
|
6
|
+
* Data definitions live in options-commands.ts and options-groups.ts;
|
|
7
|
+
* this module re-exports them and provides validation helpers.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.COMMANDS = exports.SCREENSHOT_OPTIONS = exports.OPTION_GROUPS = void 0;
|
|
11
|
+
exports.getBooleanFlags = getBooleanFlags;
|
|
12
|
+
exports.getAllowedFlags = getAllowedFlags;
|
|
13
|
+
exports.getCommandAllowedFlags = getCommandAllowedFlags;
|
|
14
|
+
exports.getCommandGroups = getCommandGroups;
|
|
15
|
+
exports.getCommandOptions = getCommandOptions;
|
|
16
|
+
exports.commandSupportsGroup = commandSupportsGroup;
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Re-exports from split modules
|
|
19
|
+
// ============================================================================
|
|
20
|
+
var options_groups_1 = require("./options-groups");
|
|
21
|
+
Object.defineProperty(exports, "OPTION_GROUPS", { enumerable: true, get: function () { return options_groups_1.OPTION_GROUPS; } });
|
|
22
|
+
Object.defineProperty(exports, "SCREENSHOT_OPTIONS", { enumerable: true, get: function () { return options_groups_1.SCREENSHOT_OPTIONS; } });
|
|
23
|
+
var options_commands_1 = require("./options-commands");
|
|
24
|
+
Object.defineProperty(exports, "COMMANDS", { enumerable: true, get: function () { return options_commands_1.COMMANDS; } });
|
|
25
|
+
const options_groups_2 = require("./options-groups");
|
|
26
|
+
const options_commands_2 = require("./options-commands");
|
|
27
|
+
const GLOBAL_FLAGS = ["help", "json", "pretty"];
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Helper Functions
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Get all allowed flags for argument parsing validation.
|
|
33
|
+
*/
|
|
34
|
+
function getBooleanFlags() {
|
|
35
|
+
const flags = new Set();
|
|
36
|
+
GLOBAL_FLAGS.forEach((flag) => flags.add(flag));
|
|
37
|
+
const addFromOptions = (options) => {
|
|
38
|
+
for (const opt of options) {
|
|
39
|
+
if (!/^--[a-z-]+$/.test(opt.flag.trim())) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
43
|
+
if (match) {
|
|
44
|
+
flags.add(match[1]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
for (const group of Object.values(options_groups_2.OPTION_GROUPS)) {
|
|
49
|
+
addFromOptions(group.options);
|
|
50
|
+
}
|
|
51
|
+
for (const cmd of Object.values(options_commands_2.COMMANDS)) {
|
|
52
|
+
if (cmd.options) {
|
|
53
|
+
addFromOptions(cmd.options);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return flags;
|
|
57
|
+
}
|
|
58
|
+
function getAllowedFlags() {
|
|
59
|
+
const flags = new Set();
|
|
60
|
+
for (const flag of getBooleanFlags()) {
|
|
61
|
+
flags.add(flag);
|
|
62
|
+
}
|
|
63
|
+
// Add value flags from option groups
|
|
64
|
+
for (const group of Object.values(options_groups_2.OPTION_GROUPS)) {
|
|
65
|
+
for (const opt of group.options) {
|
|
66
|
+
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
67
|
+
if (match)
|
|
68
|
+
flags.add(match[1]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Add value flags from command options
|
|
72
|
+
for (const cmd of Object.values(options_commands_2.COMMANDS)) {
|
|
73
|
+
for (const opt of cmd.options || []) {
|
|
74
|
+
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
75
|
+
if (match)
|
|
76
|
+
flags.add(match[1]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
flags.add("profile");
|
|
80
|
+
return flags;
|
|
81
|
+
}
|
|
82
|
+
function getCommandAllowedFlags(command) {
|
|
83
|
+
const flags = new Set(GLOBAL_FLAGS);
|
|
84
|
+
flags.add("profile");
|
|
85
|
+
const meta = options_commands_2.COMMANDS[command];
|
|
86
|
+
const addOptions = (options) => {
|
|
87
|
+
if (!options) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const opt of options) {
|
|
91
|
+
const match = opt.flag.match(/^--([a-z-]+)/);
|
|
92
|
+
if (match) {
|
|
93
|
+
flags.add(match[1]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
if (meta?.groups) {
|
|
98
|
+
for (const groupKey of meta.groups) {
|
|
99
|
+
addOptions(options_groups_2.OPTION_GROUPS[groupKey]?.options);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
addOptions(meta?.options);
|
|
103
|
+
return flags;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the option groups for a command.
|
|
107
|
+
*/
|
|
108
|
+
function getCommandGroups(command) {
|
|
109
|
+
const meta = options_commands_2.COMMANDS[command];
|
|
110
|
+
return meta?.groups ? [...meta.groups] : [];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get command-specific options (not from groups).
|
|
114
|
+
*/
|
|
115
|
+
function getCommandOptions(command) {
|
|
116
|
+
const meta = options_commands_2.COMMANDS[command];
|
|
117
|
+
return meta?.options ? [...meta.options] : [];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a command supports a specific option group.
|
|
121
|
+
*/
|
|
122
|
+
function commandSupportsGroup(command, group) {
|
|
123
|
+
const meta = options_commands_2.COMMANDS[command];
|
|
124
|
+
return meta?.groups?.includes(group) ?? false;
|
|
125
|
+
}
|
|
@@ -23,8 +23,9 @@ function printJson(payload, pretty = true) {
|
|
|
23
23
|
function errorOut(message) {
|
|
24
24
|
const hints = {
|
|
25
25
|
"Unknown option: --format": "Use --json for JSON output. --format is only for report.",
|
|
26
|
+
"ENOENT": "Native host not running. Ensure the browser extension is loaded and active. If you recently upgraded, run: tabctl setup",
|
|
26
27
|
};
|
|
27
|
-
const hint = hints[message];
|
|
28
|
+
const hint = Object.entries(hints).find(([key]) => message.includes(key))?.[1];
|
|
28
29
|
if (hint) {
|
|
29
30
|
printJson({ ok: false, error: { message, hint } });
|
|
30
31
|
}
|
|
@@ -45,13 +46,13 @@ function setupStdoutErrorHandling() {
|
|
|
45
46
|
function emitVersionWarnings(response, fallbackAction) {
|
|
46
47
|
const hostVersion = typeof response.version === "string" ? response.version : null;
|
|
47
48
|
if (hostVersion && hostVersion !== version_1.VERSION) {
|
|
48
|
-
process.stderr.write(`[tabctl] version mismatch: cli ${version_1.VERSION}, host ${hostVersion}\n`);
|
|
49
|
+
process.stderr.write(`[tabctl] version mismatch: cli ${version_1.VERSION}, host ${hostVersion}. Run: tabctl setup\n`);
|
|
49
50
|
}
|
|
50
51
|
const data = response.data;
|
|
51
52
|
const extensionVersion = data && typeof data.extensionVersion === "string" ? data.extensionVersion : null;
|
|
52
53
|
const extensionComponent = data && typeof data.extensionComponent === "string" ? data.extensionComponent : null;
|
|
53
54
|
if (extensionVersion && hostVersion && extensionVersion !== hostVersion) {
|
|
54
|
-
process.stderr.write(`[tabctl] version mismatch: host ${hostVersion}, extension ${extensionVersion}\n`);
|
|
55
|
+
process.stderr.write(`[tabctl] version mismatch: host ${hostVersion}, extension ${extensionVersion}. Reload the extension in your browser\n`);
|
|
55
56
|
}
|
|
56
57
|
if (extensionComponent && extensionComponent !== "extension") {
|
|
57
58
|
process.stderr.write(`[tabctl] unexpected extension component: ${extensionComponent}\n`);
|
|
@@ -59,6 +60,6 @@ function emitVersionWarnings(response, fallbackAction) {
|
|
|
59
60
|
const action = response.action || fallbackAction;
|
|
60
61
|
const extensionExpected = !["history", "version"].includes(action);
|
|
61
62
|
if (extensionExpected && !extensionVersion) {
|
|
62
|
-
process.stderr.write("[tabctl] extension version unavailable
|
|
63
|
+
process.stderr.write("[tabctl] extension version unavailable. Reload the extension in your browser\n");
|
|
63
64
|
}
|
|
64
65
|
}
|