New:Socket for Asana Is Now Available.Learn more
Get Started

tabctl

Package Overview
Dependencies
Maintainers
1
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tabctl - npm Package Compare versions

Comparing version
0.6.0-rc.5
to
0.6.0-rc.6
+94
-30
dist/extension/lib/content.js

@@ -7,2 +7,3 @@ "use strict";

exports.extractPageMeta = extractPageMeta;
exports.extractPageMarkdown = extractPageMarkdown;
exports.extractSelectorSignal = extractSelectorSignal;

@@ -65,2 +66,9 @@ function delay(ms) {

}
async function extractPageMarkdown(tabId, timeoutMs, maxHtmlChars) {
const result = await executeWithTimeout(tabId, timeoutMs, (cap) => {
const raw = document.documentElement?.outerHTML || "";
return raw.length > cap ? raw.slice(0, cap) : raw;
}, [maxHtmlChars]);
return typeof result === "string" ? result : "";
}
async function extractSelectorSignal(tabId, specs, timeoutMs, selectorValueMaxLength) {

@@ -75,2 +83,5 @@ if (!specs.length) {

const hints = {};
const stringCap = typeof maxLen === "number" && maxLen > 0 ? maxLen : 500;
const htmlCap = typeof maxLen === "number" && maxLen > 0 ? maxLen : 4096;
const normalizeStringValue = (value, cap) => value.replace(/\s+/g, " ").trim().slice(0, cap);
for (const raw of rawSpecs) {

@@ -88,2 +99,5 @@ const selector = typeof raw.selector === "string" ? raw.selector : "";

const textModes = new Set(["", "contains", "exact", "starts-with"]);
const styleProps = Array.isArray(raw.styleProps)
? raw.styleProps.filter((prop) => typeof prop === "string").map((prop) => prop.trim()).filter(Boolean)
: [];
if (!textModes.has(normalizedTextMode)) {

@@ -96,12 +110,2 @@ errors[name] = `Unsupported textMode: ${textMode || "unknown"}`;

const elements = Array.from(document.querySelectorAll(selector));
if (!elements.length) {
missing.push(name);
if (selector.includes(":contains(")) {
hints[name] = "CSS :contains() is not supported; use selector text filters or a different selector.";
}
else {
hints[name] = "No matches found; capture a screenshot for context or adjust the selector.";
}
continue;
}
const matchesText = (el) => {

@@ -121,2 +125,16 @@ if (!text) {

const filtered = text ? elements.filter(matchesText) : elements;
if (attr === "count") {
values[name] = filtered.length;
continue;
}
if (!elements.length) {
missing.push(name);
if (selector.includes(":contains(")) {
hints[name] = "CSS :contains() is not supported; use selector text filters or a different selector.";
}
else {
hints[name] = "No matches found; capture a screenshot for context or adjust the selector.";
}
continue;
}
if (!filtered.length) {

@@ -128,33 +146,79 @@ missing.push(name);

const getValue = (el) => {
let value = "";
if (attr === "text") {
value = el.textContent || "";
return normalizeStringValue(el.textContent || "", stringCap);
}
else if (attr === "href-url" || attr === "src-url") {
if (attr === "html") {
return normalizeStringValue(el.outerHTML || "", htmlCap);
}
if (attr === "href-url" || attr === "src-url") {
const rawValue = el.getAttribute(attr === "href-url" ? "href" : "src") || "";
if (!rawValue) {
value = "";
return "";
}
else {
try {
const resolved = new URL(rawValue, document.baseURI);
if (resolved.protocol === "http:" || resolved.protocol === "https:") {
value = resolved.toString();
}
else {
value = "";
}
try {
const resolved = new URL(rawValue, document.baseURI);
if (resolved.protocol === "http:" || resolved.protocol === "https:") {
return normalizeStringValue(resolved.toString(), stringCap);
}
catch {
value = "";
}
return "";
}
catch {
return "";
}
}
else {
value = el.getAttribute(attr) || "";
if (attr === "value") {
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
return el.value;
}
return null;
}
return value.replace(/\s+/g, " ").trim().slice(0, maxLen);
if (attr === "box") {
const rect = el.getBoundingClientRect();
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
};
}
if (attr === "styles") {
if (!styleProps.length) {
return {};
}
const computed = window.getComputedStyle(el);
const selected = {};
for (const prop of styleProps) {
selected[prop] = computed.getPropertyValue(prop);
}
return selected;
}
if (attr === "visible") {
const computed = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
const styleVisible = computed.display !== "none" && computed.visibility !== "hidden" && computed.opacity !== "0";
const hasRenderedBox = rect.width > 0 || rect.height > 0;
return styleVisible && hasRenderedBox;
}
if (attr === "enabled") {
const candidate = el;
return candidate.disabled !== true && el.getAttribute("aria-disabled") !== "true";
}
if (attr === "checked") {
if (el instanceof HTMLInputElement) {
return el.checked;
}
return el.getAttribute("aria-checked") === "true";
}
return normalizeStringValue(el.getAttribute(attr) || "", stringCap);
};
if (attr === "box") {
values[name] = getValue(filtered[0]);
continue;
}
if (all) {
values[name] = filtered.map(getValue).filter((val) => val.length > 0);
values[name] = filtered.map(getValue).filter((val) => typeof val !== "string" || val.length > 0);
}

@@ -161,0 +225,0 @@ else {

@@ -22,3 +22,3 @@ {

},
"version_name": "0.6.0-rc.5"
"version_name": "0.6.0-rc.6"
}
{
"name": "tabctl",
"version": "0.6.0-rc.5",
"version": "0.6.0-rc.6",
"description": "CLI tool to manage and analyze browser tabs",

@@ -45,2 +45,3 @@ "license": "MIT",

"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",
"test:smoke": "node scripts/smoke-test.js",
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" ",

@@ -56,7 +57,8 @@ "prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true"

"optionalDependencies": {
"tabctl-win32-x64": "0.6.0-rc.5"
"tabctl-win32-x64": "0.6.0-rc.6"
},
"dependencies": {
"markdown-for-agents": "^1.3.4",
"normalize-url": "^8.1.1"
}
}

@@ -150,2 +150,7 @@ # tabctl

Read-only browser features include:
- `inspectTabs` for page metadata and selector-based reads
- `readTabs` for page HTML → Markdown conversion with best-effort extraction
- `reportTabs`, `captureScreenshots`, and browser-state history queries for summaries and context
See [CLI.md](CLI.md) for the full command reference, options, and examples.

@@ -165,2 +170,8 @@

# Inspect selectors with typed attrs and filters
tabctl query 'query { inspectTabs(windowId: 123, selectors: [{ name: "prices", selector: ".price", attr: "text", all: true }, { name: "buy_now_visible", selector: "button.buy-now", attr: "visible" }, { name: "buy_now_style", selector: "button.buy-now", attr: "styles", styleProps: ["color", "background-color"] }, { name: "review_count", selector: ".review", attr: "count" }, { name: "email_value", selector: "input[type=email]", attr: "value" }]) { entries { tabId url signals { name valueJson } } } }'
# Read tab content as Markdown (best-effort extraction by default)
tabctl query 'query { readTabs(windowId: 123, extract: true, maxChars: 30000) { entries { tabId title url markdown chars truncated extracted error } } }'
# Generate reports

@@ -420,5 +431,6 @@ tabctl query '{ reportTabs(windowId: 123) { entries { tabId title url description } } }'

- Reports include short descriptions from page metadata and a fallback snippet.
- `inspectTabs` supports `page-meta` and `selector` signals.
- `inspectTabs` supports `page-meta` plus selector reads with `all`, `text`, `textMode`, `styleProps`, and attrs such as `html`, `value`, `count`, `box`, `styles`, `visible`, `enabled`, and `checked`.
- `readTabs` converts main-frame page HTML to Markdown, with best-effort content extraction enabled by default and status surfaced through `extracted`/`error` fields.
- `captureScreenshots` returns tile metadata and image data from GraphQL.
- `undoAction` accepts either an explicit `txid` or `latest: true`.
- `tabctl history --json` returns a top-level JSON array.

Sorry, the diff of this file is too big to display