From 0db392036da0179db32af126039451a0778cc93c Mon Sep 17 00:00:00 2001 From: freelw Date: Tue, 21 Jul 2026 17:53:20 +0800 Subject: [PATCH 1/2] fix: support context descriptions --- README.md | 4 +-- SKILL.md | 2 +- browser_cli/agent_skill/SKILL.md | 4 +-- browser_cli/cli.py | 49 +++++++++++++++++++++++++------- tests/test_cli.py | 40 ++++++++++++++++++++++---- 5 files changed, 79 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 71aed2d..d9afd34 100644 --- a/README.md +++ b/README.md @@ -645,7 +645,7 @@ Session management: ```bash browser-cli session create -browser-cli session create --create-context +browser-cli session create --create-context --context-description "Office login context" browser-cli session create --context-id --context-mode read_write browser-cli session create --context-metadata-json '{"purpose":"codex-login"}' --context-selection newest --create-context-if-missing browser-cli session list --status active @@ -658,7 +658,7 @@ Context management: ```bash browser-cli context create -browser-cli context create --metadata-json '{"purpose":"codex"}' +browser-cli context create --description "Office login context" --metadata-json '{"purpose":"codex"}' browser-cli context list --metadata-json '{"purpose":"codex-login"}' --selection newest --include-reuse-state browser-cli context get --context-id browser-cli context status --context-id diff --git a/SKILL.md b/SKILL.md index 515ef69..fa9ac9b 100644 --- a/SKILL.md +++ b/SKILL.md @@ -288,7 +288,7 @@ browser-cli commands --workflow persistent_login_state Then use the returned context-selection steps, typically: ```bash -browser-cli context create +browser-cli context create --description "Office login context" browser-cli context status --context-id browser-cli session create --context-id --context-mode read_write browser-cli session create --context-metadata-json '{"purpose":"codex-login"}' --context-selection newest --create-context-if-missing --context-mode read_write diff --git a/browser_cli/agent_skill/SKILL.md b/browser_cli/agent_skill/SKILL.md index 515ef69..8bc64d3 100644 --- a/browser_cli/agent_skill/SKILL.md +++ b/browser_cli/agent_skill/SKILL.md @@ -288,7 +288,7 @@ browser-cli commands --workflow persistent_login_state Then use the returned context-selection steps, typically: ```bash -browser-cli context create +browser-cli context create --description "Office login context" browser-cli context status --context-id browser-cli session create --context-id --context-mode read_write browser-cli session create --context-metadata-json '{"purpose":"codex-login"}' --context-selection newest --create-context-if-missing --context-mode read_write @@ -403,7 +403,7 @@ browser-cli session keepalive --session-id Context lifecycle: ```bash -browser-cli context create +browser-cli context create --description "Office login context" browser-cli context list --metadata-json '{"purpose":"codex-login"}' --selection newest --include-reuse-state browser-cli context get --context-id browser-cli context status --context-id diff --git a/browser_cli/cli.py b/browser_cli/cli.py index 73f369b..aa59586 100644 --- a/browser_cli/cli.py +++ b/browser_cli/cli.py @@ -8167,6 +8167,7 @@ def _select_or_create_context_for_session( *, command: str, metadata_filter: dict[str, Any], + description: str | None = None, status: str | None, limit: int, selection_strategy: str, @@ -8221,7 +8222,10 @@ def _select_or_create_context_for_session( if create_if_missing: try: - context = admin.create_context(metadata=metadata_filter or None) + context_kwargs: dict[str, Any] = {"metadata": metadata_filter or None} + if description is not None: + context_kwargs["description"] = description + context = admin.create_context(**context_kwargs) except Exception as exc: _failure_from_exception(command, exc) created_context = _model_payload(context) @@ -11127,6 +11131,7 @@ def cmd_session_create(args: argparse.Namespace) -> None: admin, command=command, metadata_filter=context_metadata_filter, + description=args.context_description, status=args.context_status, limit=args.context_limit, selection_strategy=args.context_selection, @@ -11142,13 +11147,16 @@ def cmd_session_create(args: argparse.Namespace) -> None: context_reuse=context_reuse, ) - result = admin.create_session( - context_id=context_id, - create_context=create_context, - context_mode=args.context_mode, - browser_mode=args.browser_mode, - metadata=args.metadata, - ) + session_kwargs: dict[str, Any] = { + "context_id": context_id, + "create_context": create_context, + "context_mode": args.context_mode, + "browser_mode": args.browser_mode, + "metadata": args.metadata, + } + if args.context_description is not None: + session_kwargs["context_description"] = args.context_description + result = admin.create_session(**session_kwargs) except Exception as exc: _failure_from_exception(command, exc) payload = _model_payload(result) @@ -11206,7 +11214,10 @@ def cmd_session_keepalive(args: argparse.Namespace) -> None: def cmd_context_create(args: argparse.Namespace) -> None: command = "context.create" try: - context = LexmountBrowserAdmin().create_context(metadata=args.metadata) + context_kwargs: dict[str, Any] = {"metadata": args.metadata} + if args.description is not None: + context_kwargs["description"] = args.description + context = LexmountBrowserAdmin().create_context(**context_kwargs) except Exception as exc: _failure_from_exception(command, exc) payload = _model_payload(context) @@ -11376,7 +11387,10 @@ def cmd_context_pick(args: argparse.Namespace) -> None: if args.create_if_missing: try: - context = admin.create_context(metadata=metadata_filter or None) + context_kwargs: dict[str, Any] = {"metadata": metadata_filter or None} + if args.description is not None: + context_kwargs["description"] = args.description + context = admin.create_context(**context_kwargs) except Exception as exc: _failure_from_exception(command, exc) created_context = _model_payload(context) @@ -29856,6 +29870,13 @@ def _add_session_create_args(parser: argparse.ArgumentParser) -> None: default="read_write", type=_normalize_context_mode, ) + parser.add_argument( + "--context-description", + help=( + "Optional UTF-8 description for a newly created context when using " + "--create-context or --create-context-if-missing." + ), + ) parser.add_argument( "--browser-mode", default="normal", @@ -29972,6 +29993,10 @@ def _add_context_commands(subparsers: argparse._SubParsersAction[Any]) -> None: type=_parse_metadata_json, help="JSON object sent as context metadata", ) + context_create.add_argument( + "--description", + help="Optional UTF-8 description for the persistent context.", + ) context_create.set_defaults(func=cmd_context_create) context_list = context_subparsers.add_parser("list", help="List contexts") @@ -30040,6 +30065,10 @@ def _add_context_commands(subparsers: argparse._SubParsersAction[Any]) -> None: action="store_true", help="Create a context with the metadata filter when none is reusable.", ) + context_pick.add_argument( + "--description", + help="Optional UTF-8 description when --create-if-missing creates a context.", + ) context_pick.add_argument( "--dry-run", action="store_true", diff --git a/tests/test_cli.py b/tests/test_cli.py index 4081126..a0a3b9d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13371,6 +13371,7 @@ def create_session( context_mode: str, browser_mode: str, metadata: dict[str, Any] | None, + context_description: str | None = None, ) -> DummyModel: observed.update( { @@ -13379,6 +13380,7 @@ def create_session( "context_mode": context_mode, "browser_mode": browser_mode, "metadata": metadata, + "context_description": context_description, } ) return DummyModel( @@ -13400,6 +13402,8 @@ def create_session( "--create-context", "--context-mode", "read_only", + "--context-description", + "Office login context", "--browser-mode", "light", "--metadata-json", @@ -13414,6 +13418,7 @@ def create_session( "context_mode": "read_only", "browser_mode": "light", "metadata": {"owner": "codex"}, + "context_description": "Office login context", } payload = json.loads(capsys.readouterr().out) assert payload["ok"] is True @@ -14088,9 +14093,17 @@ def create_context( self, *, metadata: dict[str, Any] | None, + description: str | None = None, ) -> DummyModel: - calls.append(("create", {"metadata": metadata})) - return DummyModel({"context_id": "ctx1", "metadata": metadata}) + calls.append(("create", {"metadata": metadata, "description": description})) + return DummyModel( + { + "context_id": "ctx1", + "description": description, + "display_name": description or "ctx1", + "metadata": metadata, + } + ) def list_contexts( self, @@ -14118,9 +14131,20 @@ def delete_context(self, context_id: str) -> None: monkeypatch.setattr("browser_cli.cli.LexmountBrowserAdmin", lambda: FakeAdmin()) with pytest.raises(SystemExit) as exc_info: - cli_main(["context", "create", "--metadata-json", '{"purpose":"test"}']) + cli_main( + [ + "context", + "create", + "--metadata-json", + '{"purpose":"test"}', + "--description", + "Office login context", + ] + ) assert exc_info.value.code == 0 - assert json.loads(capsys.readouterr().out)["context"]["context_id"] == "ctx1" + created = json.loads(capsys.readouterr().out)["context"] + assert created["context_id"] == "ctx1" + assert created["description"] == "Office login context" with pytest.raises(SystemExit) as exc_info: cli_main(["context", "list", "--status", "available", "--limit", "5"]) @@ -14141,7 +14165,13 @@ def delete_context(self, context_id: str) -> None: assert json.loads(capsys.readouterr().out)["deleted"] is True assert calls == [ - ("create", {"metadata": {"purpose": "test"}}), + ( + "create", + { + "metadata": {"purpose": "test"}, + "description": "Office login context", + }, + ), ("list", {"status": "available", "limit": 5}), ("get", {"context_id": "ctx1"}), ("delete", {"context_id": "ctx1"}), From 6c7d8c2974fae4a7392e2d57e4a14446a45a0259 Mon Sep 17 00:00:00 2001 From: freelw Date: Wed, 22 Jul 2026 09:23:33 +0800 Subject: [PATCH 2/2] fix: bump browser cli version for context descriptions --- browser_cli/__init__.py | 2 +- pyproject.toml | 2 +- tests/test_cli.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/browser_cli/__init__.py b/browser_cli/__init__.py index 7d02baa..9623cd9 100644 --- a/browser_cli/__init__.py +++ b/browser_cli/__init__.py @@ -2,4 +2,4 @@ __all__ = ["__version__"] -__version__ = "0.3.7" +__version__ = "0.3.8" diff --git a/pyproject.toml b/pyproject.toml index e57035a..7427914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browser-cli" -version = "0.3.7" +version = "0.3.8" description = "Standalone CLI for operating Lexmount browser sessions" readme = "README.md" requires-python = ">=3.11" diff --git a/tests/test_cli.py b/tests/test_cli.py index a0a3b9d..a923e1c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -81,7 +81,7 @@ def test_version_command_falls_back_to_package_constant( assert exc_info.value.code == 0 payload = json.loads(capsys.readouterr().out) assert payload["command"] == "version" - assert payload["version"] == "0.3.7" + assert payload["version"] == "0.3.8" assert payload["version_source"] == "package_fallback" assert payload["lex_browser_runtime_version"] == "unknown" assert payload["lex_browser_runtime_version_known"] is False @@ -9884,7 +9884,7 @@ def list_sessions(self, *, status: str | None) -> DummyModel: assert exc_info.value.code == 0 payload = json.loads(capsys.readouterr().out) checks = _checks_by_name(payload) - assert checks["browser_cli"]["version"] == "0.3.7" + assert checks["browser_cli"]["version"] == "0.3.8" assert checks["browser_cli"]["version_known"] is True assert checks["browser_cli"]["version_source"] == "package_fallback" assert checks["lex_browser_runtime"]["version"] == "unknown"