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.
- package/README.md +135 -35
- package/dist/extension/background.js +179 -3155
- package/dist/extension/lib/content.js +0 -115
- package/dist/extension/lib/screenshot.js +0 -93
- package/dist/extension/manifest.json +2 -2
- package/package.json +13 -5
- package/dist/cli/lib/args.js +0 -141
- package/dist/cli/lib/client.js +0 -83
- package/dist/cli/lib/commands/doctor.js +0 -134
- package/dist/cli/lib/commands/index.js +0 -51
- package/dist/cli/lib/commands/list.js +0 -159
- package/dist/cli/lib/commands/meta.js +0 -229
- package/dist/cli/lib/commands/params-groups.js +0 -48
- package/dist/cli/lib/commands/params-move.js +0 -44
- package/dist/cli/lib/commands/params.js +0 -314
- package/dist/cli/lib/commands/profile.js +0 -91
- package/dist/cli/lib/commands/setup.js +0 -294
- package/dist/cli/lib/constants.js +0 -30
- package/dist/cli/lib/help.js +0 -205
- package/dist/cli/lib/options-commands.js +0 -274
- package/dist/cli/lib/options-groups.js +0 -41
- package/dist/cli/lib/options.js +0 -125
- package/dist/cli/lib/output.js +0 -147
- package/dist/cli/lib/pagination.js +0 -55
- package/dist/cli/lib/policy-filter.js +0 -202
- package/dist/cli/lib/policy.js +0 -91
- package/dist/cli/lib/report.js +0 -61
- package/dist/cli/lib/response.js +0 -235
- package/dist/cli/lib/scope.js +0 -250
- package/dist/cli/lib/snapshot.js +0 -216
- package/dist/cli/lib/types.js +0 -2
- package/dist/cli/tabctl.js +0 -475
- package/dist/extension/lib/archive.js +0 -444
- package/dist/extension/lib/deps.js +0 -4
- package/dist/extension/lib/groups.js +0 -529
- package/dist/extension/lib/inspect.js +0 -252
- package/dist/extension/lib/move.js +0 -342
- package/dist/extension/lib/tabs.js +0 -456
- package/dist/extension/lib/undo-handlers.js +0 -447
- package/dist/host/host.bundle.js +0 -670
- package/dist/host/host.js +0 -143
- package/dist/host/host.sh +0 -5
- package/dist/host/launcher/go.mod +0 -3
- package/dist/host/launcher/main.go +0 -109
- package/dist/host/lib/handlers.js +0 -327
- package/dist/host/lib/undo.js +0 -60
- package/dist/shared/config.js +0 -134
- package/dist/shared/extension-sync.js +0 -170
- package/dist/shared/profiles.js +0 -78
- package/dist/shared/version.js +0 -8
- package/dist/shared/wrapper-health.js +0 -132
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Content script execution utilities — extracted from background.ts (pure structural refactor).
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.SETTLE_POLL_INTERVAL_MS = exports.SETTLE_STABILITY_MS = void 0;
|
|
5
|
-
exports.isScriptableUrl = isScriptableUrl;
|
|
6
4
|
exports.delay = delay;
|
|
7
5
|
exports.executeWithTimeout = executeWithTimeout;
|
|
8
6
|
exports.extractPageMeta = extractPageMeta;
|
|
9
7
|
exports.extractSelectorSignal = extractSelectorSignal;
|
|
10
|
-
exports.waitForTabLoad = waitForTabLoad;
|
|
11
|
-
exports.waitForDomReady = waitForDomReady;
|
|
12
|
-
exports.waitForSettle = waitForSettle;
|
|
13
|
-
exports.waitForTabReady = waitForTabReady;
|
|
14
|
-
exports.SETTLE_STABILITY_MS = 500;
|
|
15
|
-
exports.SETTLE_POLL_INTERVAL_MS = 50;
|
|
16
|
-
function isScriptableUrl(url) {
|
|
17
|
-
return typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"));
|
|
18
|
-
}
|
|
19
8
|
function delay(ms) {
|
|
20
9
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
10
|
}
|
|
@@ -184,107 +173,3 @@ async function extractSelectorSignal(tabId, specs, timeoutMs, selectorValueMaxLe
|
|
|
184
173
|
}
|
|
185
174
|
return result;
|
|
186
175
|
}
|
|
187
|
-
function waitForTabLoad(tabId, timeoutMs) {
|
|
188
|
-
return new Promise((resolve) => {
|
|
189
|
-
let settled = false;
|
|
190
|
-
const done = () => {
|
|
191
|
-
if (settled) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
settled = true;
|
|
195
|
-
chrome.tabs.onUpdated.removeListener(onUpdated);
|
|
196
|
-
resolve();
|
|
197
|
-
};
|
|
198
|
-
const onUpdated = (updatedTabId, info) => {
|
|
199
|
-
if (updatedTabId === tabId && info.status === "complete") {
|
|
200
|
-
done();
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
chrome.tabs.onUpdated.addListener(onUpdated);
|
|
204
|
-
chrome.tabs.get(tabId).then((tab) => {
|
|
205
|
-
if (tab.status === "complete") {
|
|
206
|
-
done();
|
|
207
|
-
}
|
|
208
|
-
}).catch(() => {
|
|
209
|
-
done();
|
|
210
|
-
});
|
|
211
|
-
setTimeout(done, timeoutMs);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
async function waitForDomReady(tabId, timeoutMs) {
|
|
215
|
-
const result = await executeWithTimeout(tabId, timeoutMs, () => {
|
|
216
|
-
if (document.readyState === "interactive" || document.readyState === "complete") {
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
return new Promise((resolve) => {
|
|
220
|
-
const onReady = () => {
|
|
221
|
-
document.removeEventListener("DOMContentLoaded", onReady);
|
|
222
|
-
resolve(true);
|
|
223
|
-
};
|
|
224
|
-
document.addEventListener("DOMContentLoaded", onReady, { once: true });
|
|
225
|
-
setTimeout(() => {
|
|
226
|
-
document.removeEventListener("DOMContentLoaded", onReady);
|
|
227
|
-
resolve(false);
|
|
228
|
-
}, Math.max(0, timeoutMs - 50));
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
if (result === null) {
|
|
232
|
-
await delay(Math.min(200, Math.max(50, Math.floor(timeoutMs / 10))));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
async function waitForSettle(tabId, timeoutMs) {
|
|
236
|
-
const startTime = Date.now();
|
|
237
|
-
let lastUrl = "";
|
|
238
|
-
let lastTitle = "";
|
|
239
|
-
let stableStart = Date.now();
|
|
240
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
241
|
-
const tab = await chrome.tabs.get(tabId).catch(() => null);
|
|
242
|
-
if (!tab)
|
|
243
|
-
return;
|
|
244
|
-
const currentUrl = tab.url || "";
|
|
245
|
-
const currentTitle = tab.title || "";
|
|
246
|
-
// Reset stability timer if URL or title changed
|
|
247
|
-
if (currentUrl !== lastUrl || currentTitle !== lastTitle) {
|
|
248
|
-
lastUrl = currentUrl;
|
|
249
|
-
lastTitle = currentTitle;
|
|
250
|
-
stableStart = Date.now();
|
|
251
|
-
}
|
|
252
|
-
else if (isScriptableUrl(currentUrl) &&
|
|
253
|
-
tab.status === "complete" &&
|
|
254
|
-
Date.now() - stableStart >= exports.SETTLE_STABILITY_MS) {
|
|
255
|
-
// Page is loaded, URL is valid, and stable for long enough
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
await delay(exports.SETTLE_POLL_INTERVAL_MS);
|
|
259
|
-
}
|
|
260
|
-
// Timeout reached, continue anyway
|
|
261
|
-
}
|
|
262
|
-
async function waitForTabReady(tabId, params, fallbackTimeoutMs) {
|
|
263
|
-
const waitFor = typeof params.waitFor === "string" ? params.waitFor.trim().toLowerCase() : "";
|
|
264
|
-
if (!waitFor || waitFor === "none") {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const timeoutRaw = Number(params.waitTimeoutMs);
|
|
268
|
-
const timeoutMs = Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.floor(timeoutRaw) : fallbackTimeoutMs;
|
|
269
|
-
// settle mode handles its own URL checking, so skip the early return
|
|
270
|
-
if (waitFor === "settle") {
|
|
271
|
-
await waitForSettle(tabId, timeoutMs);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
try {
|
|
275
|
-
const tab = await chrome.tabs.get(tabId);
|
|
276
|
-
if (!isScriptableUrl(tab.url)) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (waitFor === "load") {
|
|
284
|
-
await waitForTabLoad(tabId, timeoutMs);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (waitFor === "dom") {
|
|
288
|
-
await waitForDomReady(tabId, timeoutMs);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
@@ -13,7 +13,6 @@ exports.captureVisible = captureVisible;
|
|
|
13
13
|
exports.getPageMetrics = getPageMetrics;
|
|
14
14
|
exports.scrollToPosition = scrollToPosition;
|
|
15
15
|
exports.captureTabTiles = captureTabTiles;
|
|
16
|
-
exports.screenshotTabs = screenshotTabs;
|
|
17
16
|
exports.SCREENSHOT_TILE_MAX_DIM = 2000;
|
|
18
17
|
exports.SCREENSHOT_MAX_BYTES = 2_000_000;
|
|
19
18
|
exports.SCREENSHOT_QUALITY = 80;
|
|
@@ -273,95 +272,3 @@ async function captureTabTiles(tab, options, deps) {
|
|
|
273
272
|
await scrollToPosition(tabId, exports.SCREENSHOT_PROCESS_TIMEOUT_MS, startScrollX, startScrollY, deps);
|
|
274
273
|
return tiles;
|
|
275
274
|
}
|
|
276
|
-
async function screenshotTabs(params, requestId, deps) {
|
|
277
|
-
const snapshot = await deps.getTabSnapshot();
|
|
278
|
-
const selection = deps.selectTabsByScope(snapshot, params);
|
|
279
|
-
if (selection.error) {
|
|
280
|
-
throw selection.error;
|
|
281
|
-
}
|
|
282
|
-
const mode = params.mode === "full" ? "full" : "viewport";
|
|
283
|
-
const format = params.format === "jpeg" ? "jpeg" : "png";
|
|
284
|
-
const qualityRaw = Number(params.quality);
|
|
285
|
-
const quality = Number.isFinite(qualityRaw) ? Math.min(100, Math.max(0, Math.floor(qualityRaw))) : exports.SCREENSHOT_QUALITY;
|
|
286
|
-
const tileMaxDimRaw = Number(params.tileMaxDim);
|
|
287
|
-
const tileMaxDim = Number.isFinite(tileMaxDimRaw) && tileMaxDimRaw > 0 ? Math.floor(tileMaxDimRaw) : exports.SCREENSHOT_TILE_MAX_DIM;
|
|
288
|
-
const adjustedTileMaxDim = tileMaxDim < 50 ? 50 : tileMaxDim;
|
|
289
|
-
const maxBytesRaw = Number(params.maxBytes);
|
|
290
|
-
const maxBytes = Number.isFinite(maxBytesRaw) && maxBytesRaw > 0 ? Math.floor(maxBytesRaw) : exports.SCREENSHOT_MAX_BYTES;
|
|
291
|
-
const adjustedMaxBytes = maxBytes < 50_000 ? 50_000 : maxBytes;
|
|
292
|
-
const progressEnabled = params.progress === true;
|
|
293
|
-
const tabs = selection.tabs;
|
|
294
|
-
const entries = [];
|
|
295
|
-
let totalTiles = 0;
|
|
296
|
-
const startedAt = Date.now();
|
|
297
|
-
for (let index = 0; index < tabs.length; index += 1) {
|
|
298
|
-
const tab = tabs[index];
|
|
299
|
-
const tabId = tab.tabId;
|
|
300
|
-
const url = tab.url;
|
|
301
|
-
if (!deps.isScriptableUrl(url)) {
|
|
302
|
-
entries.push({
|
|
303
|
-
tabId,
|
|
304
|
-
windowId: tab.windowId,
|
|
305
|
-
groupId: tab.groupId,
|
|
306
|
-
url: tab.url,
|
|
307
|
-
title: tab.title,
|
|
308
|
-
error: { message: "unsupported_url" },
|
|
309
|
-
tiles: [],
|
|
310
|
-
});
|
|
311
|
-
if (progressEnabled) {
|
|
312
|
-
deps.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs.length, tabId });
|
|
313
|
-
}
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
let tiles = [];
|
|
317
|
-
let error = null;
|
|
318
|
-
try {
|
|
319
|
-
const windowId = tab.windowId;
|
|
320
|
-
const activeTabs = await chrome.tabs.query({ windowId, active: true });
|
|
321
|
-
const activeTabId = activeTabs[0]?.id ?? null;
|
|
322
|
-
if (activeTabId && activeTabId !== tabId) {
|
|
323
|
-
await chrome.tabs.update(tabId, { active: true });
|
|
324
|
-
await deps.delay(exports.SCREENSHOT_SCROLL_DELAY_MS);
|
|
325
|
-
}
|
|
326
|
-
try {
|
|
327
|
-
await deps.waitForTabReady(tabId, params, exports.SCREENSHOT_PROCESS_TIMEOUT_MS);
|
|
328
|
-
tiles = await captureTabTiles(tab, { mode, format, quality, tileMaxDim: adjustedTileMaxDim, maxBytes: adjustedMaxBytes }, deps);
|
|
329
|
-
}
|
|
330
|
-
finally {
|
|
331
|
-
if (activeTabId && activeTabId !== tabId) {
|
|
332
|
-
await chrome.tabs.update(activeTabId, { active: true });
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
const message = err instanceof Error ? err.message : "capture_failed";
|
|
338
|
-
error = { message };
|
|
339
|
-
}
|
|
340
|
-
totalTiles += tiles.length;
|
|
341
|
-
entries.push({
|
|
342
|
-
tabId: tab.tabId,
|
|
343
|
-
windowId: tab.windowId,
|
|
344
|
-
groupId: tab.groupId,
|
|
345
|
-
url: tab.url,
|
|
346
|
-
title: tab.title,
|
|
347
|
-
tiles,
|
|
348
|
-
...(error ? { error } : {}),
|
|
349
|
-
});
|
|
350
|
-
if (progressEnabled) {
|
|
351
|
-
deps.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs.length, tabId });
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
generatedAt: Date.now(),
|
|
356
|
-
totals: { tabs: tabs.length, tiles: totalTiles },
|
|
357
|
-
meta: {
|
|
358
|
-
durationMs: Date.now() - startedAt,
|
|
359
|
-
mode,
|
|
360
|
-
format,
|
|
361
|
-
quality: format === "jpeg" ? quality : null,
|
|
362
|
-
tileMaxDim: adjustedTileMaxDim,
|
|
363
|
-
maxBytes: adjustedMaxBytes,
|
|
364
|
-
},
|
|
365
|
-
entries,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Tab Control",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "Archive and manage browser tabs with CLI support",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"tabs",
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"background": {
|
|
20
20
|
"service_worker": "background.js"
|
|
21
21
|
},
|
|
22
|
-
"version_name": "0.
|
|
22
|
+
"version_name": "0.6.0-alpha.10"
|
|
23
23
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tabctl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-alpha.10",
|
|
4
4
|
"description": "CLI tool to manage and analyze browser tabs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,13 +27,21 @@
|
|
|
27
27
|
"dist/extension"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "node scripts/gen-version.js && tsc -p tsconfig.json && node scripts/copy-artifacts.js && node scripts/
|
|
30
|
+
"build": "node scripts/gen-version.js && tsc -p tsconfig.json && node scripts/copy-artifacts.js && node scripts/bundle-extension.js && cargo build --manifest-path rust/Cargo.toml --workspace",
|
|
31
|
+
"build:launcher": "node scripts/build-launcher.js",
|
|
31
32
|
"bump:major": "node scripts/bump-version.js major",
|
|
32
33
|
"bump:minor": "node scripts/bump-version.js minor",
|
|
33
34
|
"bump:patch": "node scripts/bump-version.js patch",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
35
|
+
"bump:alpha": "node scripts/bump-version.js alpha",
|
|
36
|
+
"check:targets": "bash scripts/check-targets.sh",
|
|
37
|
+
"bump:rc": "node scripts/bump-version.js rc",
|
|
38
|
+
"bump:stable": "node scripts/bump-version.js stable",
|
|
39
|
+
"rust:check": "cargo check --manifest-path rust/Cargo.toml --workspace --all-targets",
|
|
40
|
+
"rust:test": "cargo test --manifest-path rust/Cargo.toml --workspace --all-targets",
|
|
41
|
+
"rust:verify": "cargo fmt --manifest-path rust/Cargo.toml --all -- --check && cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings && npm run rust:test",
|
|
42
|
+
"test": "npm run build && npm run rust:verify",
|
|
43
|
+
"test:unit": "npm run rust:test",
|
|
44
|
+
"test:integration": "cargo test --manifest-path rust/Cargo.toml --test browser_integration --test browser_coverage --test browser_primitives --test local_commands -- --ignored --nocapture --test-threads=1",
|
|
37
45
|
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" ",
|
|
38
46
|
"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true"
|
|
39
47
|
},
|
package/dist/cli/lib/args.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeGroupColor = normalizeGroupColor;
|
|
4
|
-
exports.normalizeSignals = normalizeSignals;
|
|
5
|
-
exports.validateSignals = validateSignals;
|
|
6
|
-
exports.parseArgs = parseArgs;
|
|
7
|
-
const constants_1 = require("./constants");
|
|
8
|
-
const options_1 = require("./options");
|
|
9
|
-
const output_1 = require("./output");
|
|
10
|
-
function normalizeGroupColor(value) {
|
|
11
|
-
if (typeof value !== "string") {
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
const trimmed = value.trim().toLowerCase();
|
|
15
|
-
if (!trimmed) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
if (!constants_1.GROUP_COLORS.has(trimmed)) {
|
|
19
|
-
(0, output_1.errorOut)(`Invalid color: ${value}. Use one of: ${Array.from(constants_1.GROUP_COLORS).join(", ")}`);
|
|
20
|
-
}
|
|
21
|
-
return trimmed;
|
|
22
|
-
}
|
|
23
|
-
function normalizeSignals(value) {
|
|
24
|
-
if (!Array.isArray(value)) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
return value.map((signal) => String(signal).trim()).filter(Boolean);
|
|
28
|
-
}
|
|
29
|
-
function validateSignals(signals) {
|
|
30
|
-
for (const signal of signals) {
|
|
31
|
-
if (!constants_1.SUPPORTED_SIGNAL_SET.has(signal)) {
|
|
32
|
-
(0, output_1.errorOut)(`Unknown signal: ${signal}. Use one of: ${constants_1.SUPPORTED_SIGNALS.join(", ")}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function normalizeCommand(value) {
|
|
37
|
-
if (!value) {
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
if (value === "groups" || value === "group") {
|
|
41
|
-
return "group-list";
|
|
42
|
-
}
|
|
43
|
-
const meta = options_1.COMMANDS[value];
|
|
44
|
-
if (!meta?.aliases || meta.aliases.length === 0) {
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
|
-
return meta.aliases[0] ?? value;
|
|
48
|
-
}
|
|
49
|
-
function parseArgs(argv) {
|
|
50
|
-
const args = [...argv];
|
|
51
|
-
let command;
|
|
52
|
-
const options = { _: [] };
|
|
53
|
-
const warnings = [];
|
|
54
|
-
const pendingFlags = [];
|
|
55
|
-
const allowedFlags = (0, options_1.getAllowedFlags)();
|
|
56
|
-
const booleanFlags = (0, options_1.getBooleanFlags)();
|
|
57
|
-
while (args.length > 0) {
|
|
58
|
-
const arg = args.shift();
|
|
59
|
-
if (!arg.startsWith("--")) {
|
|
60
|
-
if (!command) {
|
|
61
|
-
command = normalizeCommand(arg);
|
|
62
|
-
if (command) {
|
|
63
|
-
const commandAllowedFlags = (0, options_1.getCommandAllowedFlags)(command);
|
|
64
|
-
for (const pending of pendingFlags) {
|
|
65
|
-
if (!commandAllowedFlags.has(pending)) {
|
|
66
|
-
warnings.push(`--${pending} is not supported by ${command}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
options._.push(arg);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const key = arg.slice(2);
|
|
76
|
-
if (!allowedFlags.has(key)) {
|
|
77
|
-
if (key === "format") {
|
|
78
|
-
(0, output_1.errorOut)("Unknown option: --format");
|
|
79
|
-
}
|
|
80
|
-
(0, output_1.errorOut)(`Unknown option: --${key}`);
|
|
81
|
-
}
|
|
82
|
-
if (command) {
|
|
83
|
-
const commandAllowedFlags = (0, options_1.getCommandAllowedFlags)(command);
|
|
84
|
-
if (!commandAllowedFlags.has(key)) {
|
|
85
|
-
warnings.push(`--${key} is not supported by ${command}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
pendingFlags.push(key);
|
|
90
|
-
}
|
|
91
|
-
// Boolean flags (no value needed)
|
|
92
|
-
if (booleanFlags.has(key)) {
|
|
93
|
-
options[key] = true;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
// Value required
|
|
97
|
-
const value = args.shift();
|
|
98
|
-
if (value == null) {
|
|
99
|
-
(0, output_1.errorOut)(`Missing value for --${key}`);
|
|
100
|
-
}
|
|
101
|
-
// Repeatable flags (accumulate into arrays)
|
|
102
|
-
if (key === "signal") {
|
|
103
|
-
if (!options.signal) {
|
|
104
|
-
options.signal = [];
|
|
105
|
-
}
|
|
106
|
-
options.signal.push(value);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (key === "tab") {
|
|
110
|
-
if (!options.tab) {
|
|
111
|
-
options.tab = [];
|
|
112
|
-
}
|
|
113
|
-
options.tab.push(value);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (key === "agent") {
|
|
117
|
-
if (!options.agent) {
|
|
118
|
-
options.agent = [];
|
|
119
|
-
}
|
|
120
|
-
options.agent.push(value);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (key === "url") {
|
|
124
|
-
if (!options.url) {
|
|
125
|
-
options.url = [];
|
|
126
|
-
}
|
|
127
|
-
options.url.push(value);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (key === "selector") {
|
|
131
|
-
if (!options.selector) {
|
|
132
|
-
options.selector = [];
|
|
133
|
-
}
|
|
134
|
-
options.selector.push(value);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
// Single value flags
|
|
138
|
-
options[key] = value;
|
|
139
|
-
}
|
|
140
|
-
return { command, options, warnings };
|
|
141
|
-
}
|
package/dist/cli/lib/client.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createRequestId = createRequestId;
|
|
7
|
-
exports.sendRequest = sendRequest;
|
|
8
|
-
exports.fetchSnapshot = fetchSnapshot;
|
|
9
|
-
exports.sendFireAndForget = sendFireAndForget;
|
|
10
|
-
const node_net_1 = __importDefault(require("node:net"));
|
|
11
|
-
const constants_1 = require("./constants");
|
|
12
|
-
function createRequestId() {
|
|
13
|
-
return `req-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
14
|
-
}
|
|
15
|
-
function sendRequest(payload, onProgress) {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const { socketPath } = (0, constants_1.resolveConfig)();
|
|
18
|
-
const client = node_net_1.default.createConnection(socketPath);
|
|
19
|
-
let buffer = "";
|
|
20
|
-
client.on("connect", () => {
|
|
21
|
-
client.write(`${JSON.stringify(payload)}\n`);
|
|
22
|
-
});
|
|
23
|
-
client.on("data", (data) => {
|
|
24
|
-
buffer += data;
|
|
25
|
-
let index;
|
|
26
|
-
while ((index = buffer.indexOf("\n")) >= 0) {
|
|
27
|
-
const line = buffer.slice(0, index).trim();
|
|
28
|
-
buffer = buffer.slice(index + 1);
|
|
29
|
-
if (!line) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
let response;
|
|
33
|
-
try {
|
|
34
|
-
response = JSON.parse(line);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
client.end();
|
|
38
|
-
client.destroy();
|
|
39
|
-
reject(error);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (response.progress && onProgress) {
|
|
43
|
-
onProgress(response);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
client.end();
|
|
47
|
-
client.destroy();
|
|
48
|
-
resolve(response);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
client.on("error", (error) => {
|
|
53
|
-
reject(error);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
async function fetchSnapshot() {
|
|
58
|
-
const response = await sendRequest({ id: createRequestId(), action: "list", params: {} });
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
return response.data;
|
|
63
|
-
}
|
|
64
|
-
/** Send a request without waiting for a response (fire-and-forget). */
|
|
65
|
-
function sendFireAndForget(payload) {
|
|
66
|
-
try {
|
|
67
|
-
const { socketPath } = (0, constants_1.resolveConfig)();
|
|
68
|
-
const client = node_net_1.default.createConnection(socketPath);
|
|
69
|
-
client.on("connect", () => {
|
|
70
|
-
client.write(`${JSON.stringify(payload)}\n`);
|
|
71
|
-
// Unref after write so Node can exit without waiting for response
|
|
72
|
-
client.unref();
|
|
73
|
-
const timer = setTimeout(() => { client.end(); client.destroy(); }, 200);
|
|
74
|
-
timer.unref();
|
|
75
|
-
});
|
|
76
|
-
client.on("error", () => {
|
|
77
|
-
// Silently ignore — this is best-effort
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Silently ignore
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Doctor command handler: diagnose and repair profile health.
|
|
4
|
-
*
|
|
5
|
-
* Checks each profile's wrapper for valid Node/host paths, verifies
|
|
6
|
-
* extension sync status, and optionally auto-repairs broken wrappers.
|
|
7
|
-
*/
|
|
8
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.runDoctor = runDoctor;
|
|
13
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
-
const config_1 = require("../../../shared/config");
|
|
16
|
-
const profiles_1 = require("../../../shared/profiles");
|
|
17
|
-
const wrapper_health_1 = require("../../../shared/wrapper-health");
|
|
18
|
-
const extension_sync_1 = require("../../../shared/extension-sync");
|
|
19
|
-
const setup_1 = require("./setup");
|
|
20
|
-
const output_1 = require("../output");
|
|
21
|
-
function checkProfile(name, entry, fix) {
|
|
22
|
-
const wrapperPath = (0, wrapper_health_1.resolveWrapperPath)(entry.dataDir);
|
|
23
|
-
const check = (0, wrapper_health_1.checkWrapper)(wrapperPath);
|
|
24
|
-
const issues = [...check.issues];
|
|
25
|
-
let fixed = false;
|
|
26
|
-
if (fix && !check.ok && check.info) {
|
|
27
|
-
const needsNodeFix = !node_fs_1.default.existsSync(check.info.nodePath);
|
|
28
|
-
const needsHostFix = !node_fs_1.default.existsSync(check.info.hostPath);
|
|
29
|
-
if (needsNodeFix || needsHostFix) {
|
|
30
|
-
const newNodePath = needsNodeFix ? process.execPath : check.info.nodePath;
|
|
31
|
-
let newHostPath = check.info.hostPath;
|
|
32
|
-
if (needsHostFix) {
|
|
33
|
-
// Use the stable bundled host path
|
|
34
|
-
try {
|
|
35
|
-
const config = (0, config_1.resolveConfig)();
|
|
36
|
-
newHostPath = (0, extension_sync_1.resolveInstalledHostPath)(config.baseDataDir);
|
|
37
|
-
if (!node_fs_1.default.existsSync(newHostPath)) {
|
|
38
|
-
issues.push(`Bundled host not found at ${newHostPath} — run: tabctl setup --browser ${entry.browser}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
issues.push("Could not resolve bundled host path");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
(0, setup_1.writeWrapper)(newNodePath, newHostPath, check.info.profileName, node_path_1.default.dirname(wrapperPath));
|
|
47
|
-
fixed = true;
|
|
48
|
-
// Update issue messages to show they were fixed
|
|
49
|
-
const fixedIssues = [];
|
|
50
|
-
if (needsNodeFix) {
|
|
51
|
-
fixedIssues.push(`Fixed Node path: ${check.info.nodePath} → ${newNodePath}`);
|
|
52
|
-
}
|
|
53
|
-
if (needsHostFix) {
|
|
54
|
-
fixedIssues.push(`Fixed host path: ${check.info.hostPath} → ${newHostPath}`);
|
|
55
|
-
}
|
|
56
|
-
// Replace original issues with fixed messages
|
|
57
|
-
issues.length = 0;
|
|
58
|
-
issues.push(...fixedIssues);
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
issues.push(`Failed to fix wrapper: ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
ok: check.ok || fixed,
|
|
67
|
-
browser: entry.browser,
|
|
68
|
-
dataDir: entry.dataDir,
|
|
69
|
-
wrapperPath,
|
|
70
|
-
issues,
|
|
71
|
-
fixed,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function runDoctor(options, prettyOutput) {
|
|
75
|
-
const fix = options.fix === true;
|
|
76
|
-
const config = (0, config_1.resolveConfig)();
|
|
77
|
-
const registry = (0, profiles_1.loadProfiles)(config.configDir);
|
|
78
|
-
const profileNames = Object.keys(registry.profiles);
|
|
79
|
-
if (profileNames.length === 0) {
|
|
80
|
-
(0, output_1.errorOut)("No profiles configured. Run: tabctl setup --browser <edge|chrome>");
|
|
81
|
-
}
|
|
82
|
-
// Check each profile
|
|
83
|
-
const profiles = {};
|
|
84
|
-
for (const name of profileNames) {
|
|
85
|
-
profiles[name] = checkProfile(name, registry.profiles[name], fix);
|
|
86
|
-
}
|
|
87
|
-
// Check extension sync status
|
|
88
|
-
let extensionCheck;
|
|
89
|
-
try {
|
|
90
|
-
const sync = (0, extension_sync_1.checkExtensionSync)(config.baseDataDir);
|
|
91
|
-
extensionCheck = {
|
|
92
|
-
ok: !sync.needsSync,
|
|
93
|
-
synced: !sync.needsSync,
|
|
94
|
-
bundledVersion: sync.bundledVersion,
|
|
95
|
-
installedVersion: sync.installedVersion,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
extensionCheck = {
|
|
100
|
-
ok: false,
|
|
101
|
-
synced: false,
|
|
102
|
-
bundledVersion: null,
|
|
103
|
-
installedVersion: null,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
// Summary
|
|
107
|
-
const total = profileNames.length;
|
|
108
|
-
const healthy = Object.values(profiles).filter(p => p.ok).length;
|
|
109
|
-
const broken = total - healthy;
|
|
110
|
-
const fixed = Object.values(profiles).filter(p => p.fixed).length;
|
|
111
|
-
const allOk = broken === 0 && extensionCheck.ok;
|
|
112
|
-
(0, output_1.printJson)({
|
|
113
|
-
ok: allOk,
|
|
114
|
-
action: "doctor",
|
|
115
|
-
data: {
|
|
116
|
-
profiles,
|
|
117
|
-
extension: extensionCheck,
|
|
118
|
-
summary: { total, healthy, broken, fixed },
|
|
119
|
-
},
|
|
120
|
-
}, prettyOutput);
|
|
121
|
-
// Helpful stderr hints
|
|
122
|
-
if (!allOk && !fix) {
|
|
123
|
-
const brokenNames = Object.entries(profiles)
|
|
124
|
-
.filter(([, p]) => !p.ok)
|
|
125
|
-
.map(([n]) => n);
|
|
126
|
-
if (brokenNames.length > 0) {
|
|
127
|
-
process.stderr.write(`\nBroken profiles: ${brokenNames.join(", ")}\n`);
|
|
128
|
-
process.stderr.write("Run: tabctl doctor --fix\n\n");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (fixed > 0) {
|
|
132
|
-
process.stderr.write(`\nFixed ${fixed} profile(s). Verify: tabctl ping\n\n`);
|
|
133
|
-
}
|
|
134
|
-
}
|