diff --git a/pythonlib/README.md b/pythonlib/README.md index b3d03ae..db03620 100644 --- a/pythonlib/README.md +++ b/pythonlib/README.md @@ -81,7 +81,6 @@ camoufox gui https://github.com/user-attachments/assets/992b1830-6b21-4024-9165-728854df1473 -
See help message @@ -90,33 +89,33 @@ $ python -m camoufox --help Usage: python -m camoufox [OPTIONS] COMMAND [ARGS]... -╭─ Options ─────────────────────────────────────────────────────────────────────────╮ -│ --help Show this message and exit. │ -╰───────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ──────────────────────────────────────────────────────────────────────────────────╮ -│ active Print the current active version │ -│ fetch Install the active version, or a specific version. │ -│ gui Launch the Camoufox Manager GUI (requires PySide6) │ -│ list List Camoufox versions │ -│ path Print the install directory path │ -│ remove Remove Camoufox data directory, or an installed browser version │ -│ Or, remove a specific version: │ -│ Select a version: camoufox remove --select │ -│ Remove a version: camoufox remove official/stable/134.0.2-beta.20 │ -│ server Launch a Playwright server │ -│ set Interactive selector for versions and settings │ -│ Or, pass a specifier to activate directly: │ -│ Pin version: camoufox set official/stable/134.0.2-beta.20 │ -│ Auto-update channel: camoufox set official/stable │ -│ sync Sync available versions from remote repositories. │ -│ test Open the Playwright inspector │ -│ version Display version, package, browser, and storage info │ -╰─────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────────────────────────────────────────╮ +│ --help Show this message and exit. │ +╰───────────────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ────────────────────────────────────────────────────────────────────────────╮ +│ active Print the current active version │ +│ fetch Install the active version, or a specific version │ +│ gui Launch the Camoufox Manager GUI (requires PySide6) │ +│ list List Camoufox versions │ +│ path Print the install directory path │ +│ remove Remove downloaded data. By default, this removes everything. │ +│ Pass --select to pick a browser version to remove. │ +│ server Launch a Playwright server │ +│ set Set the active Camoufox version to use & fetch. │ +│ By default, this opens an interactive selector for versions and settings. │ +│ You can also pass a specifier to activate directly: │ +│ Pin version: │ +│ camoufox set official/stable/134.0.2-beta.20 │ +│ Automatically find latest in a channel source: │ +│ camoufox set official/stable │ +│ sync Sync available versions from remote repositories │ +│ test Open the Playwright inspector │ +│ version Display version, package, browser, and storage info │ +╰───────────────────────────────────────────────────────────────────────────────────────╯ ```
- ### `sync` Pull a list of release assets from GitHub. @@ -253,7 +252,7 @@ Python Packages Playwright v1.57.1.dev0+g732639b35.d20251217 Browser Active official/stable/135.0.1-beta.24 - Browser v135.0.1-beta.24 + Current browser v135.0.1-beta.24 Installed Yes Latest in official/stable? Yes Last Sync 2026-03-07 00:23 diff --git a/pythonlib/camoufox/__main__.py b/pythonlib/camoufox/__main__.py index 4873073..fc4fa86 100644 --- a/pythonlib/camoufox/__main__.py +++ b/pythonlib/camoufox/__main__.py @@ -226,7 +226,7 @@ def cli() -> None: ) def sync(spoof_os, spoof_arch): """ - Sync available versions from remote repositories. + Sync available versions from remote repositories """ if spoof_os == "auto": spoof_os = None @@ -239,7 +239,7 @@ def sync(spoof_os, spoof_arch): @click.argument("version", default=None, required=False) def fetch(version): """ - Install the active version, or a specific version. + Install the active version, or a specific version \b Examples: @@ -343,8 +343,6 @@ def _set_channel(repo_name: str, channel_type: str): config = load_config() config["channel"] = f"{repo_name}/{channel_type}" config.pop("pinned", None) - save_config(config) - click.secho(f"Channel: {repo_name.lower()}/{channel_type}", fg="cyan", bold=True) # Check if latest for this channel is already installed is_pre = channel_type == "prerelease" @@ -358,11 +356,18 @@ def _set_channel(repo_name: str, channel_type: str): latest_build = candidates[0]["build"] for inst in list_installed(): if inst.version.build == latest_build and inst.repo_name == repo_name.lower(): - set_active(inst.relative_path) + config["active_version"] = inst.relative_path + save_config(config) + click.secho( + f"Channel: {repo_name.lower()}/{channel_type}", fg="cyan", bold=True + ) click.secho(f"Using latest: {inst.channel_path} (installed)", fg="green") return break + config.pop("active_version", None) + save_config(config) + click.secho(f"Channel: {repo_name.lower()}/{channel_type}", fg="cyan", bold=True) click.secho("Run 'camoufox fetch' to install latest.", fg="yellow") @@ -373,13 +378,15 @@ def _set_pinned(repo_name: str, channel_type: str, ver_data: dict, inst): config = load_config() config["channel"] = f"{repo_name}/{channel_type}" config["pinned"] = f"{ver_data['version']}-{ver_data['build']}" - save_config(config) ver_str = f"{ver_data['version']}-{ver_data['build']}" display = f"{repo_name.lower()}/{channel_type}/{ver_str}" if inst: - set_active(inst.relative_path) + config["active_version"] = inst.relative_path + save_config(config) click.secho(f"Pinned: {display} (installed)", fg="green") else: + config.pop("active_version", None) + save_config(config) click.secho(f"Pinned: {display}", fg="cyan", bold=True) click.secho("Run 'camoufox fetch' to install.", fg="yellow") @@ -390,10 +397,13 @@ def _set_pinned(repo_name: str, channel_type: str, ver_data: dict, inst): def set_cmd(specifier, geoip): """ \b - Interactive selector for versions and settings - Or, pass a specifier to activate directly: - Pin version: camoufox set official/stable/134.0.2-beta.20 - Auto-update channel: camoufox set official/stable + Set the active Camoufox version to use & fetch. + By default, this opens an interactive selector for versions and settings. + You can also pass a specifier to activate directly: + Pin version: + camoufox set official/stable/134.0.2-beta.20 + Automatically find latest in a channel source: + camoufox set official/stable """ if geoip: _select_geoip_source() @@ -704,10 +714,8 @@ def _list_all(_show_paths: bool): def remove(version_path, select, yes): """ \b - Remove Camoufox data directory, or an installed browser version - Or, remove a specific version: - Select a version: camoufox remove --select - Remove a version: camoufox remove official/stable/134.0.2-beta.20 + Remove downloaded data. By default, this removes everything. + Pass --select to pick a browser version to remove. """ import shutil @@ -862,9 +870,9 @@ class VersionInfo: # Browser version if active_v: - self._row("Browser", f"v{active_v.version.full_string}") + self._row("Current browser", f"v{active_v.version.full_string}") else: - self._row("Browser", "—", style="dim") + self._row("Current browser", "Not installed", style="dim") # Is installed? if active_v: @@ -927,7 +935,7 @@ class VersionInfo: def _dir_size(self, path) -> str: if not path.exists(): - return "—" + return "Nothing here" total = sum(f.stat().st_size for f in path.rglob("*") if f.is_file()) for unit in ("B", "KB", "MB"): if total < 1024: @@ -968,21 +976,28 @@ def active_cmd(): """ Print the current active version """ - installed = list_installed() - for v in installed: - if v.is_active: - click.echo(v.channel_path) - return - config = load_config() pinned = config.get("pinned") channel = config.get("channel") or get_default_channel() + if pinned: - click.echo(f"{channel.lower()}/{pinned} ", nl=False) - rprint("(not installed)", fg="yellow") + # Check if the pinned version is installed + display = f"{channel.lower()}/{pinned}" + target = _find_installed(display) + if target: + click.echo(target.channel_path) + else: + click.echo(f"{display} ", nl=False) + rprint("(not fetched)", fg="yellow") else: + # Using channel, so find active installed version + installed = list_installed() + for v in installed: + if v.is_active: + click.echo(v.channel_path) + return click.echo(f"{channel.lower()} ", nl=False) - rprint("(not installed)", fg="yellow") + rprint("(not fetched)", fg="yellow") @cli.command(name="path") diff --git a/pythonlib/camoufox/gui/assets/icon.ico b/pythonlib/camoufox/gui/assets/icon.ico index ffdac08..1788048 100644 Binary files a/pythonlib/camoufox/gui/assets/icon.ico and b/pythonlib/camoufox/gui/assets/icon.ico differ diff --git a/pythonlib/camoufox/multiversion.py b/pythonlib/camoufox/multiversion.py index d78ee8d..cecb32a 100644 --- a/pythonlib/camoufox/multiversion.py +++ b/pythonlib/camoufox/multiversion.py @@ -235,7 +235,8 @@ def list_installed() -> List[InstalledVersion]: def get_active_path() -> Optional[Path]: """ - Get path to active version. Auto-selects newest if none set + Get path to active version. Returns None if no version is active. + Only auto-selects if no channel/pin was been set """ config = load_config() active = config.get('active_version') @@ -245,11 +246,13 @@ def get_active_path() -> Optional[Path]: if path.exists() and (path / 'version.json').exists(): return path - installed = list_installed() - if installed: - config['active_version'] = installed[0].relative_path - save_config(config) - return installed[0].path + # Only auto-select if user didnt set a channel or pin + if not config.get('channel') and not config.get('pinned'): + installed = list_installed() + if installed: + config['active_version'] = installed[0].relative_path + save_config(config) + return installed[0].path return None diff --git a/pythonlib/camoufox/pkgman.py b/pythonlib/camoufox/pkgman.py index 11d56cd..2b3dd2a 100644 --- a/pythonlib/camoufox/pkgman.py +++ b/pythonlib/camoufox/pkgman.py @@ -647,7 +647,15 @@ def installed_verstr() -> str: active = get_active_path() if active is None: - raise FileNotFoundError("No active version. Please run `camoufox fetch` to install.") + from .multiversion import get_default_channel, load_config + + config = load_config() + pinned = config.get("pinned") + channel = config.get("channel") or get_default_channel() + active_display = f"{channel}/{pinned}" if pinned else channel + raise CamoufoxNotInstalled( + f"{active_display} is not installed. " f"Please run `camoufox fetch` to install." + ) return Version.from_path(active).full_string @@ -670,7 +678,18 @@ def camoufox_path(download_if_missing: bool = True) -> Path: if not os.path.exists(INSTALL_DIR) or not os.listdir(INSTALL_DIR): if not download_if_missing: - raise FileNotFoundError(f"Camoufox executable not found at {INSTALL_DIR}") + from .multiversion import load_config, get_default_channel + + config = load_config() + pinned = config.get("pinned") + channel = config.get("channel") or get_default_channel() + if pinned: + active_display = f"{channel}/{pinned}" + else: + active_display = channel + raise CamoufoxNotInstalled( + f"{active_display} is not installed. " f"Please run `camoufox fetch` to install." + ) elif os.path.exists(INSTALL_DIR) and Version.from_path().is_supported(): return INSTALL_DIR