diff --git a/README.md b/README.md index 1e14649..5e89270 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ browser-cli group move 42 --backward # move group left ```sh browser-cli windows list # list all windows browser-cli windows open # open a new window -browser-cli windows open --profile Default # request a specific Chrome profile name +browser-cli windows open https://example.com # open a new window on a URL browser-cli windows rename 1 "work" # give a window a local alias browser-cli windows close 1 # close a window ``` @@ -326,6 +326,7 @@ b.group_count() # int, or BrowserCounts(...) in multi-browser mode windows = b.windows_list() # in multi-browser mode each dict has a "browser" key b.windows_rename(1, "work") b.windows_open() +b.windows_open("https://example.com") b.windows_close(1) # DOM (active tab must be http/https) @@ -400,7 +401,6 @@ bash examples/demo.sh ## Limitations - **Chrome internal pages** (`chrome://`, `brave://`, `about:`) cannot be scripted. DOM and extract commands only work on regular `http://` and `https://` pages. -- **Profile switching** via `windows open --profile` depends on browser support and does not replace launching a separate browser profile externally with `--profile-directory`. - **Multiple browser instances can be auto-distinguished, but generated aliases are temporary**. Unaliased browsers get UUID aliases from the native host, which avoids collisions but is less ergonomic than setting a stable alias with `browser-cli rename-profile --browser `. - **Supported install targets are explicit, not “all Chromium browsers”**. The installer currently supports Chrome, Chromium, Brave, Edge, and Vivaldi. Other Chromium-based browsers may use different or shared native messaging manifest locations, so they need browser-specific verification before being added safely. - **Linux and macOS only** — Windows native messaging paths are not yet handled. diff --git a/browser_cli/__init__.py b/browser_cli/__init__.py index 85351e7..22b9caa 100644 --- a/browser_cli/__init__.py +++ b/browser_cli/__init__.py @@ -293,8 +293,9 @@ class BrowserCLI: def windows_close(self, window_id: int) -> None: self._cmd("windows.close", {"windowId": window_id}) - def windows_open(self, profile: str | None = None) -> dict: - return self._cmd("windows.open", {"profile": profile}) + def windows_open(self, url: str | None = None) -> dict: + """Open a new browser window, optionally on a URL.""" + return self._cmd("windows.open", {"url": url}) # ── DOM ─────────────────────────────────────────────────────────────── diff --git a/browser_cli/commands/windows.py b/browser_cli/commands/windows.py index 76d41a9..adc9857 100644 --- a/browser_cli/commands/windows.py +++ b/browser_cli/commands/windows.py @@ -100,9 +100,9 @@ def windows_close(window_id): @windows_group.command("open") -@click.option("--profile", default=None, help="Open with a specific Chrome profile name") -def windows_open(profile): +@click.argument("url", required=False) +def windows_open(url): """Open a new browser window.""" - result = _handle("windows.open", {"profile": profile}) + result = _handle("windows.open", {"url": url}) wid = result.get("id") if isinstance(result, dict) else result - console.print(f"[green]Opened new window[/green] (id: {wid})" + (f" with profile '{profile}'" if profile else "")) + console.print(f"[green]Opened new window[/green] (id: {wid})" + (f" with {url}" if url else "")) diff --git a/extension/background.js b/extension/background.js index 6bfa276..3caa789 100644 --- a/extension/background.js +++ b/extension/background.js @@ -545,10 +545,10 @@ async function windowsClose({ windowId }) { return { windowId }; } -async function windowsOpen({ profile }) { - // profile support requires launching Chrome with --profile-directory which - // isn't possible from within an extension — we open a plain new window. - const w = await chrome.windows.create({ focused: true }); +async function windowsOpen({ url }) { + const createData = { focused: true }; + if (url) createData.url = url; + const w = await chrome.windows.create(createData); return { id: w.id }; } diff --git a/extension/manifest.json b/extension/manifest.json index 818ef8d..ea3212b 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "browser-cli", - "version": "0.5.4", + "version": "0.5.5", "description": "Control your browser from the terminal via browser-cli", "permissions": [ "tabs", diff --git a/pyproject.toml b/pyproject.toml index d64a8f8..48459ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browser-cli" -version = "0.5.4" +version = "0.5.5" description = "Control your real running browser from the terminal via a Chrome extension" requires-python = ">=3.10" dependencies = [ diff --git a/tests/test_api.py b/tests/test_api.py index 02d9323..3547437 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -471,6 +471,22 @@ class TestWindows: {"id": 2, "tabCount": 3, "state": "maximized", "browser": "work"}, ] + def test_windows_open_without_url(self, b, mock_send): + mock_send.return_value = {"id": 5} + + result = b.windows_open() + + assert result == {"id": 5} + mock_send.assert_called_once_with("windows.open", {"url": None}, profile=None) + + def test_windows_open_with_url(self, b, mock_send): + mock_send.return_value = {"id": 9} + + result = b.windows_open("https://example.com") + + assert result == {"id": 9} + mock_send.assert_called_once_with("windows.open", {"url": "https://example.com"}, profile=None) + # ── Tab model ───────────────────────────────────────────────────────────────── diff --git a/tests/test_cli.py b/tests/test_cli.py index 5c29927..e546e54 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -203,6 +203,15 @@ def test_windows_list_multi_browser_shows_browser_column(): assert "uuid-1" in result.output assert "work" in result.output + +def test_windows_open_passes_url(): + with patch("browser_cli.commands.windows.send_command", return_value={"id": 7}) as send_command: + result = CliRunner().invoke(main, ["windows", "open", "https://example.com"]) + + assert result.exit_code == 0 + assert "https://example.com" in result.output + send_command.assert_called_once_with("windows.open", {"url": "https://example.com"}, profile=None) + def test_extract_markdown_command(): with patch("browser_cli.commands.extract.send_command", return_value="# Title\n") as send_command: result = CliRunner().invoke(main, ["extract", "markdown"])