show mute status correctly when tab mute and add to get single tab status
Package Extension / package-extension (push) Successful in 17s
Build & Publish Package / publish (push) Successful in 29s
Testing / test (push) Failing after 26s

This commit is contained in:
2026-04-13 21:35:25 +02:00
parent c494e76fe2
commit edf9056430
6 changed files with 66 additions and 10 deletions
+7
View File
@@ -173,6 +173,13 @@ class BrowserCLI:
"""Switch browser focus to a tab by ID.""" """Switch browser focus to a tab by ID."""
self._cmd("tabs.active", {"tabId": tab_id}) self._cmd("tabs.active", {"tabId": tab_id})
def tabs_status(self, tab_id: int | None = None) -> Tab:
"""Return status for the active tab or a specific tab."""
data = self._cmd("tabs.status", {"tabId": tab_id})
if not isinstance(data, dict) or "id" not in data:
raise RuntimeError("No tab status returned")
return self._make_tab(data)
def tabs_mute(self, tab_id: int | None = None) -> int: def tabs_mute(self, tab_id: int | None = None) -> int:
"""Mute the active tab or a specific tab. Returns the target tab ID.""" """Mute the active tab or a specific tab. Returns the target tab ID."""
result = self._cmd("tabs.mute", {"tabId": tab_id}) result = self._cmd("tabs.mute", {"tabId": tab_id})
+17
View File
@@ -125,6 +125,23 @@ def tabs_active(tab_id):
console.print(f"[green]Switched to tab {tab_id}[/green]") console.print(f"[green]Switched to tab {tab_id}[/green]")
@tabs_group.command("status")
@click.argument("tab_id", type=int, required=False)
def tabs_status(tab_id):
"""Show status for the active tab or a specific tab."""
tab = _handle("tabs.status", {"tabId": tab_id}) or {}
table = Table(show_header=False)
table.add_column("Field", style="bold cyan")
table.add_column("Value")
table.add_row("ID", str(tab.get("id", "")))
table.add_row("Window", str(tab.get("windowId", "")))
table.add_row("Active", "yes" if tab.get("active") else "no")
table.add_row("Muted", "yes" if tab.get("muted") else "no")
table.add_row("Title", tab.get("title") or "")
table.add_row("URL", tab.get("url") or "")
console.print(table)
@tabs_group.command("filter") @tabs_group.command("filter")
@click.argument("pattern") @click.argument("pattern")
def tabs_filter(pattern): def tabs_filter(pattern):
+22 -8
View File
@@ -134,6 +134,7 @@ async function dispatch(command, args) {
case "tabs.move": return tabsMove(args); case "tabs.move": return tabsMove(args);
case "tabs.active": return tabsActive(args); case "tabs.active": return tabsActive(args);
case "tabs.active_in_window": return tabsActiveInWindow(args); case "tabs.active_in_window": return tabsActiveInWindow(args);
case "tabs.status": return tabsStatus(args);
case "tabs.filter": return tabsFilter(args); case "tabs.filter": return tabsFilter(args);
case "tabs.count": return tabsCount(args); case "tabs.count": return tabsCount(args);
case "tabs.query": return tabsQuery(args); case "tabs.query": return tabsQuery(args);
@@ -275,15 +276,10 @@ async function tabsList() {
for (const w of windows) { for (const w of windows) {
for (const t of w.tabs) { for (const t of w.tabs) {
tabs.push({ tabs.push({
id: t.id, ...tabInfo(t),
windowId: t.windowId,
windowAlias: aliases[t.windowId] || null, windowAlias: aliases[t.windowId] || null,
active: t.active,
pinned: t.pinned, pinned: t.pinned,
title: t.title,
url: t.url,
favIconUrl: t.favIconUrl, favIconUrl: t.favIconUrl,
groupId: t.groupId >= 0 ? t.groupId : null,
}); });
} }
} }
@@ -346,6 +342,11 @@ async function tabsActiveInWindow({ windowId }) {
return tabInfo(tab); return tabInfo(tab);
} }
async function tabsStatus({ tabId }) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
return tabInfo(tab);
}
async function tabsFilter({ pattern }) { async function tabsFilter({ pattern }) {
const all = await chrome.tabs.query({}); const all = await chrome.tabs.query({});
return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo); return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo);
@@ -442,13 +443,13 @@ async function tabsMergeWindows() {
} }
async function tabsMute({ tabId }) { async function tabsMute({ tabId }) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); const tab = await resolveTabForDirectAction(tabId, "mute");
await chrome.tabs.update(tab.id, { muted: true }); await chrome.tabs.update(tab.id, { muted: true });
return { tabId: tab.id, muted: true }; return { tabId: tab.id, muted: true };
} }
async function tabsUnmute({ tabId }) { async function tabsUnmute({ tabId }) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab(); const tab = await resolveTabForDirectAction(tabId, "unmute");
await chrome.tabs.update(tab.id, { muted: false }); await chrome.tabs.update(tab.id, { muted: false });
return { tabId: tab.id, muted: false }; return { tabId: tab.id, muted: false };
} }
@@ -1243,6 +1244,19 @@ async function getActiveTab() {
|| activeTabs[0]; || activeTabs[0];
} }
async function resolveTabForDirectAction(tabId, actionName) {
if (tabId != null) {
return chrome.tabs.get(tabId);
}
const allTabs = await chrome.tabs.query({});
if (allTabs.length !== 1) {
throw new Error(
`Refusing to ${actionName} without explicit tab ID when ${allTabs.length} tabs are open`
);
}
return allTabs[0];
}
async function resolveGroupId(nameOrId) { async function resolveGroupId(nameOrId) {
const asInt = parseInt(nameOrId); const asInt = parseInt(nameOrId);
if (!isNaN(asInt)) return asInt; if (!isNaN(asInt)) return asInt;
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "browser-cli", "name": "browser-cli",
"version": "0.5.10", "version": "0.5.12",
"description": "Control your browser from the terminal via browser-cli", "description": "Control your browser from the terminal via browser-cli",
"permissions": [ "permissions": [
"tabs", "tabs",
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "browser-cli" name = "browser-cli"
version = "0.5.10" version = "0.5.12"
description = "Control your real running browser from the terminal via a Chrome extension" description = "Control your real running browser from the terminal via a Chrome extension"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
+18
View File
@@ -52,6 +52,13 @@ def test_tabs_active_in_window(browser):
assert result["windowId"] == active["windowId"] assert result["windowId"] == active["windowId"]
def test_tabs_status(browser):
result = browser("tabs.status", {})
assert isinstance(result, dict)
assert "id" in result
assert "muted" in result
def test_tabs_html(browser, http_tab): def test_tabs_html(browser, http_tab):
html = browser("tabs.html", {"tabId": http_tab["id"]}) html = browser("tabs.html", {"tabId": http_tab["id"]})
assert isinstance(html, str) assert isinstance(html, str)
@@ -133,3 +140,14 @@ def test_tabs_mute_and_unmute(browser, http_tab):
listed = browser("tabs.list") listed = browser("tabs.list")
listed_tab = next(t for t in listed if t["id"] == http_tab["id"]) listed_tab = next(t for t in listed if t["id"] == http_tab["id"])
assert listed_tab["muted"] is False assert listed_tab["muted"] is False
status = browser("tabs.status", {"tabId": http_tab["id"]})
assert status["muted"] is False
def test_tabs_mute_requires_explicit_tab_when_multiple_tabs_open(browser):
opened = browser("navigate.open", {"url": "https://example.com", "background": True})
try:
with pytest.raises(RuntimeError, match="Refusing to mute without explicit tab ID"):
browser("tabs.mute", {})
finally:
browser("tabs.close", {"tabId": opened["id"]})