From 384156838eaeaf8c999f8a05a45ecc24fbfa180b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 18 May 2026 18:39:59 -0500 Subject: [PATCH 1/2] libtmux-mcp(fix[widgets/mcp-install]): Exempt libtmux-mcp from --exclude-newer in days panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: The mcp-install widget's uvx-flavored days-mode snippets emitted ``uvx --exclude-newer P7D libtmux-mcp`` without a per-package override, so a fresh release of libtmux-mcp (newer than the cooldown cutoff) made the install unresolvable — ``no versions of libtmux-mcp`` from uv's resolver. uv's hint points at ``--exclude-newer-package =`` as the per-package override; setting it to a far-future date exempts the target package while keeping the cooldown applied to transitive deps. Identical pattern to the fix on the sibling agentgrep project. what: - Insert ``--exclude-newer-package libtmux-mcp=2099-01-01`` after ``--exclude-newer `` in the uvx + days branches of _tool_command, _json_body, and _toml_body, so every uvx + days panel (CLI, JSON, TOML body shapes) carries the exemption. - Extend _cooldown_note to surface a caveat for (pipx, days) and (pip, days) panels — pip's ``--uploaded-prior-to`` is a global cutoff with no per-package override, so when the cooldown filters out a recent libtmux-mcp release the snippet stays unresolvable. The caveat points readers at the uvx snippet, which carries the exemption. - Update test_body_for_uvx_days_inserts_duration_sentinel to assert the new flag appears in the rendered command. The other uvx-days tests use substring assertions on ``"--exclude-newer"`` / ``""`` so they pass unchanged. - No changes to bypass / off panels — bypass already skips any global uv cooldown via ``--no-config``, off has no cutoff, both stay resolvable. --- CHANGES | 6 +++ docs/_ext/widgets/mcp_install.py | 65 ++++++++++++++++++++++++-------- tests/docs/test_widgets.py | 9 ++++- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 9eb2717..1c0c716 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,12 @@ The MCP install widget on the front page, {doc}`/quickstart`, and {ref}`clients` A new *Configure cooldowns* control on the install widget lets you tack a cooldown onto the snippet without leaving the page. Pick a delay in days (uv's `--exclude-newer`, pip's `--uploaded-prior-to`, pipx's `--pip-args`) to wait out community vetting before pulling a fresh release, or pick *Bypass any global cooldown* to skip a `~/.config/uv/uv.toml` cutoff and grab the latest libtmux-mcp anyway. The setting persists across pages, and the embedded *What are cooldowns?* expander links to the [Datadog Security Labs writeup](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/) and [cooldowns.dev](https://cooldowns.dev/) if you want the supply-chain context. (#31) +### Fixes + +**MCP install widget cooldown exempts libtmux-mcp** + +The mcp-install widget's `uvx`-flavored days-mode snippets now carry `--exclude-newer-package libtmux-mcp=2099-01-01` so a security-conscious cooldown stays applied to transitive deps without filtering libtmux-mcp itself out of the resolver. Without the override the snippet emitted `no versions of libtmux-mcp` whenever the latest libtmux-mcp release was newer than the cooldown window. The pipx and pip days panels also gain a caveat note pointing readers at the uvx snippet, since pip's `--uploaded-prior-to` is a global cutoff with no per-package override. + ## libtmux-mcp 0.1.0a7 (2026-05-16) ### Breaking changes diff --git a/docs/_ext/widgets/mcp_install.py b/docs/_ext/widgets/mcp_install.py index 8375288..2c77129 100644 --- a/docs/_ext/widgets/mcp_install.py +++ b/docs/_ext/widgets/mcp_install.py @@ -291,7 +291,19 @@ def _tool_command(method: Method, cooldown: Cooldown) -> str: """ if method.id == "uvx": if cooldown.id == "days": - return f"uvx --exclude-newer {_DURATION_SENTINEL} libtmux-mcp" + # ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempts + # libtmux-mcp itself from the global cutoff so a recently- + # released libtmux-mcp stays inside the resolver. Without + # this, the snippet emits ``no versions of libtmux-mcp`` + # whenever the latest libtmux-mcp release is newer than the + # cooldown window. pipx + pip have no per-package override, + # so their days panels carry a caveat note instead (see + # ``_cooldown_note``). + return ( + f"uvx --exclude-newer {_DURATION_SENTINEL}" + " --exclude-newer-package libtmux-mcp=2099-01-01" + " libtmux-mcp" + ) if cooldown.id == "bypass": return "uvx --no-config libtmux-mcp" return "uvx libtmux-mcp" @@ -343,7 +355,13 @@ def _json_body(method: Method, cooldown: Cooldown) -> str: if method.id == "uvx": command = "uvx" if cooldown.id == "days": - args = f'"--exclude-newer", "{_DURATION_SENTINEL}", "libtmux-mcp"' + # See ``_tool_command`` for the rationale on the + # ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempt. + args = ( + f'"--exclude-newer", "{_DURATION_SENTINEL}",' + ' "--exclude-newer-package", "libtmux-mcp=2099-01-01",' + ' "libtmux-mcp"' + ) else: args = '"libtmux-mcp"' elif method.id == "pipx": @@ -392,7 +410,13 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str: if method.id == "uvx": command, args_inner = "uvx", '"libtmux-mcp"' if cooldown.id == "days": - args_inner = f'"--exclude-newer", "{_DURATION_SENTINEL}", "libtmux-mcp"' + # See ``_tool_command`` for the rationale on the + # ``--exclude-newer-package libtmux-mcp=2099-01-01`` exempt. + args_inner = ( + f'"--exclude-newer", "{_DURATION_SENTINEL}",' + ' "--exclude-newer-package", "libtmux-mcp=2099-01-01",' + ' "libtmux-mcp"' + ) elif method.id == "pipx": command, args_inner = "pipx", '"run", "libtmux-mcp"' if cooldown.id == "days": @@ -415,20 +439,31 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str: def _cooldown_note(method: Method, cooldown: Cooldown) -> str | None: - """Return a one-line caveat for cells where cooldown is a no-op.""" - if cooldown.id != "bypass": - return None - if method.id == "pip": + """Return a one-line caveat for cells where the snippet has caveats.""" + if cooldown.id == "days" and method.id in {"pipx", "pip"}: + # pip's --uploaded-prior-to is a global cutoff with no + # per-package override, so a cooldown shorter than libtmux-mcp's + # most-recent-release age makes the install unresolvable. uv + # handles this via --exclude-newer-package on the uvx panels + # (see _tool_command / _json_body / _toml_body). return ( - "pip has no global cooldown, so bypass is a no-op. Use this" - " when pairing the snippet with a uv-backed parent command." - ) - if method.id == "pipx": - return ( - "pipx's default backend (pip) has no global cooldown." - " For uv-style cooldown control, install `pipx[uv]` and set" - " `UV_NO_CONFIG=1` in your shell." + "pip's `--uploaded-prior-to` is a global cutoff with no" + " per-package override. If the cooldown filters out a recent" + " release of libtmux-mcp itself, switch to the uvx snippet —" + " it exempts libtmux-mcp via `--exclude-newer-package`." ) + if cooldown.id == "bypass": + if method.id == "pip": + return ( + "pip has no global cooldown, so bypass is a no-op. Use this" + " when pairing the snippet with a uv-backed parent command." + ) + if method.id == "pipx": + return ( + "pipx's default backend (pip) has no global cooldown." + " For uv-style cooldown control, install `pipx[uv]` and set" + " `UV_NO_CONFIG=1` in your shell." + ) return None diff --git a/tests/docs/test_widgets.py b/tests/docs/test_widgets.py index b2b0d09..a1cc579 100644 --- a/tests/docs/test_widgets.py +++ b/tests/docs/test_widgets.py @@ -117,12 +117,17 @@ def test_body_for_uvx_days_inserts_duration_sentinel() -> None: uv stores the value as ``ExcludeNewerValue::Relative(ExcludeNewerSpan)`` and re-evaluates ``now - N days`` on every resolver call, so a saved - ``.mcp.json`` arg ``"P7D"`` stays fresh forever. + ``.mcp.json`` arg ``"P7D"`` stays fresh forever. The + ``--exclude-newer-package libtmux-mcp=2099-01-01`` exemption keeps + libtmux-mcp itself inside the resolver when the cutoff would + otherwise filter the package out (a fresh release vs a 7-day + cooldown window). """ client = CLIENTS[0] body, language, note = _body_for(client, METHODS[0], client.scopes[0], _DAYS) assert body == ( - "claude mcp add tmux -- uvx --exclude-newer libtmux-mcp" + "claude mcp add tmux -- uvx --exclude-newer " + " --exclude-newer-package libtmux-mcp=2099-01-01 libtmux-mcp" ) assert language == "console" assert note is None From 85ad5b8d81e1b0eed4896bdfb00614ef13d3a029 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 18 May 2026 19:19:42 -0500 Subject: [PATCH 2/2] libtmux-mcp(fix[widgets/mcp-install]): Fall pipx + pip days panels back to bare commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: ``pipx run --pip-args=--uploaded-prior-to= libtmux-mcp`` fails with ``ERROR: Could not find a version that satisfies the requirement libtmux-mcp`` whenever the cutoff filters fresh libtmux-mcp releases. Verified directly against pip 26.1.1's source (``_internal/cli/cmdoptions.py:463``): ``--uploaded-prior-to`` is a single global cutoff with no per-package override flag. pipx's ``commands/run_uv.py::_UV_TRANSLATABLE_VALUE_FLAGS`` only forwards a narrow whitelist of pip args to its uv backend (no ``--exclude-newer`` / ``--uploaded-prior-to``), so the uv backend can't carry the cooldown either. Mirrors the same fix landed in agentgrep this round. what: - ``_tool_command`` / ``_pip_prereq_for`` / ``_json_body`` / ``_toml_body`` all return the bare command for (pipx, *) and (pip, *) — no ``--pip-args=--uploaded-prior-to`` or ``--uploaded-prior-to`` flag emitted. The uvx days panels keep ``--exclude-newer P7D --exclude-newer-package libtmux-mcp=2099-01-01`` since uv has both flags. - ``_cooldown_note`` for (pipx | pip, days | bypass) now returns a single redirect note: "pip has no per-package cooldown override, so this snippet runs without cooldown enforcement. Switch to the uvx tab — it applies the cooldown to transitive deps via ``--exclude-newer`` while exempting libtmux-mcp itself via ``--exclude-newer-package``." Drops the old separate (pipx, bypass) and (pip, bypass) notes. - Remove the now-unused ``_DATE_SENTINEL`` constant and ``PIP_PREREQ_DAYS`` constant from mcp_install.py. - Update test_body_for_pipx_days_uses_pip_args_form_with_absolute_date → test_body_for_pipx_days_falls_back_to_bare_run; add test_body_for_pip_days_falls_back_to_bare_install; replace test_pip_panel_has_cooldown_aware_pip_prereq with test_pip_panel_has_bare_pip_prereq_across_modes; update test_cooldown_days_slot_filter_registered_on_widget_render to drop the date-slot assertion (no widget emits it anymore). --- CHANGES | 6 +- docs/_ext/widgets/mcp_install.py | 98 +++++++++++++------------------- tests/docs/test_widgets.py | 65 +++++++++++++++------ 3 files changed, 91 insertions(+), 78 deletions(-) diff --git a/CHANGES b/CHANGES index 1c0c716..cd19655 100644 --- a/CHANGES +++ b/CHANGES @@ -16,11 +16,11 @@ The MCP install widget on the front page, {doc}`/quickstart`, and {ref}`clients` A new *Configure cooldowns* control on the install widget lets you tack a cooldown onto the snippet without leaving the page. Pick a delay in days (uv's `--exclude-newer`, pip's `--uploaded-prior-to`, pipx's `--pip-args`) to wait out community vetting before pulling a fresh release, or pick *Bypass any global cooldown* to skip a `~/.config/uv/uv.toml` cutoff and grab the latest libtmux-mcp anyway. The setting persists across pages, and the embedded *What are cooldowns?* expander links to the [Datadog Security Labs writeup](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/) and [cooldowns.dev](https://cooldowns.dev/) if you want the supply-chain context. (#31) -### Fixes +### Documentation -**MCP install widget cooldown exempts libtmux-mcp** +**MCP install widget ships working cooldown snippets** -The mcp-install widget's `uvx`-flavored days-mode snippets now carry `--exclude-newer-package libtmux-mcp=2099-01-01` so a security-conscious cooldown stays applied to transitive deps without filtering libtmux-mcp itself out of the resolver. Without the override the snippet emitted `no versions of libtmux-mcp` whenever the latest libtmux-mcp release was newer than the cooldown window. The pipx and pip days panels also gain a caveat note pointing readers at the uvx snippet, since pip's `--uploaded-prior-to` is a global cutoff with no per-package override. +The install widget on the front page, {doc}`/quickstart`, and {ref}`clients` now emits a runnable snippet in every method × cooldown cell. uvx days panels apply the cooldown to transitive dependencies while exempting libtmux-mcp itself, so a fresh release stays installable. pipx and pip days panels fall back to the bare install command — neither tool exposes a per-package cooldown override today — and their cooldown note redirects readers to the uvx tab when they want true cooldown enforcement. (#58) ## libtmux-mcp 0.1.0a7 (2026-05-16) diff --git a/docs/_ext/widgets/mcp_install.py b/docs/_ext/widgets/mcp_install.py index 2c77129..4fa68bd 100644 --- a/docs/_ext/widgets/mcp_install.py +++ b/docs/_ext/widgets/mcp_install.py @@ -239,13 +239,10 @@ class Panel: # re-evaluates ``now - N days`` at every resolver call, so the saved # ``.mcp.json`` arg ``"P7D"`` stays fresh forever. pip 26.1+ does the # same at flag-parse time on every invocation. -# * ```` is used by pipx days bodies because pipx 1.8.0 -# bundles a pip older than 26.1, which rejects the duration form with -# ``Invalid isoformat``. The absolute date is computed in JS from -# ``today - savedDays``; the build-time default drifts daily but -# ``widget.js`` refreshes the slot on every page load. +# pipx and pip days panels don't embed a sentinel — they fall back to +# the bare command because pip has no per-package cooldown override +# (see ``_tool_command`` / ``_pip_prereq_for`` / ``_cooldown_note``). _DURATION_SENTINEL = "" -_DATE_SENTINEL = "" def default_cooldown_date(days: int) -> str: @@ -263,21 +260,18 @@ def default_cooldown_date(days: int) -> str: PIP_PREREQ_OFF: str = "pip install --user --upgrade libtmux libtmux-mcp" -PIP_PREREQ_DAYS: str = ( - f"pip install --user --upgrade --uploaded-prior-to {_DURATION_SENTINEL}" - " libtmux libtmux-mcp" -) def _pip_prereq_for(cooldown: Cooldown) -> str: """Return the prereq ``pip install`` line for the given cooldown mode. - ``bypass`` falls through to the ``off`` form: pip has no global cooldown - config to bypass, so the prereq is identical and the cooldown note on - the panel explains the situation. + All three modes (off / days / bypass) emit the same bare install + line. pip's ``--uploaded-prior-to`` has no per-package override, + so a days-mode cutoff filters fresh libtmux-mcp releases out of + the resolver — the snippet would fail to install. The per-panel + ``_cooldown_note`` redirects users to the uvx snippet, which + carries ``--exclude-newer-package`` for true cooldown enforcement. """ - if cooldown.id == "days": - return PIP_PREREQ_DAYS return PIP_PREREQ_OFF @@ -308,17 +302,19 @@ def _tool_command(method: Method, cooldown: Cooldown) -> str: return "uvx --no-config libtmux-mcp" return "uvx libtmux-mcp" if method.id == "pipx": - if cooldown.id == "days": - # pipx 1.8.0 bundles pip <26.1, which doesn't accept the - # duration form — emit an absolute date instead. JS recomputes - # ``today - savedDays`` on every page load. - return ( - f"pipx run --pip-args=--uploaded-prior-to={_DATE_SENTINEL} libtmux-mcp" - ) - # off + bypass (pipx default backend has no global cooldown) + # pipx's pip backend has no per-package cooldown override, and + # pipx's uv backend doesn't translate ``--uploaded-prior-to`` or + # ``--exclude-newer`` from ``--pip-args`` (see pipx's + # ``commands/run_uv.py::_UV_TRANSLATABLE_VALUE_FLAGS``). Both + # paths fail on fresh libtmux-mcp releases when a days-mode + # cutoff filters the target package out. All three modes emit + # the bare ``pipx run`` command; the per-cell ``_cooldown_note`` + # redirects users to the uvx snippet for true cooldown + # enforcement. return "pipx run libtmux-mcp" - # pip method: the cooldown applies on the prereq line above, not on - # the registered command. The register step is just ``libtmux-mcp``. + # pip method: same per-package-override limitation. The prereq line + # (``_pip_prereq_for``) emits the bare ``pip install``; the register + # step is just ``libtmux-mcp``. return "libtmux-mcp" @@ -365,14 +361,10 @@ def _json_body(method: Method, cooldown: Cooldown) -> str: else: args = '"libtmux-mcp"' elif method.id == "pipx": + # All three modes emit the bare ``pipx run`` args (no + # ``--pip-args``) — see ``_tool_command`` for the reasoning. command = "pipx" - if cooldown.id == "days": - args = ( - '"run", "--pip-args=--uploaded-prior-to=' - f'{_DATE_SENTINEL}", "libtmux-mcp"' - ) - else: - args = '"run", "libtmux-mcp"' + args = '"run", "libtmux-mcp"' else: # pip command = "libtmux-mcp" args = None @@ -418,12 +410,9 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str: ' "libtmux-mcp"' ) elif method.id == "pipx": + # All three modes emit the same args — see ``_tool_command`` for + # the per-package-override rationale. command, args_inner = "pipx", '"run", "libtmux-mcp"' - if cooldown.id == "days": - args_inner = ( - f'"run", "--pip-args=--uploaded-prior-to={_DATE_SENTINEL}",' - ' "libtmux-mcp"' - ) else: # pip command, args_inner = "libtmux-mcp", None @@ -440,30 +429,21 @@ def _toml_body(method: Method, cooldown: Cooldown) -> str: def _cooldown_note(method: Method, cooldown: Cooldown) -> str | None: """Return a one-line caveat for cells where the snippet has caveats.""" - if cooldown.id == "days" and method.id in {"pipx", "pip"}: - # pip's --uploaded-prior-to is a global cutoff with no - # per-package override, so a cooldown shorter than libtmux-mcp's - # most-recent-release age makes the install unresolvable. uv - # handles this via --exclude-newer-package on the uvx panels - # (see _tool_command / _json_body / _toml_body). + if method.id in {"pipx", "pip"} and cooldown.id in {"days", "bypass"}: + # pip's ``--uploaded-prior-to`` has no per-package override (so + # ``days`` mode would filter the target package out of the + # resolver for fresh releases) and there's no global cooldown + # for pip to bypass either. Both modes fall back to the bare + # command; the note redirects users to the uvx snippet, which + # carries ``--exclude-newer-package`` for true per-package + # cooldown enforcement. return ( - "pip's `--uploaded-prior-to` is a global cutoff with no" - " per-package override. If the cooldown filters out a recent" - " release of libtmux-mcp itself, switch to the uvx snippet —" - " it exempts libtmux-mcp via `--exclude-newer-package`." + "pip has no per-package cooldown override, so this snippet" + " runs without cooldown enforcement. Switch to the uvx tab —" + " it applies the cooldown to transitive deps via" + " `--exclude-newer` while exempting libtmux-mcp itself via" + " `--exclude-newer-package`." ) - if cooldown.id == "bypass": - if method.id == "pip": - return ( - "pip has no global cooldown, so bypass is a no-op. Use this" - " when pairing the snippet with a uv-backed parent command." - ) - if method.id == "pipx": - return ( - "pipx's default backend (pip) has no global cooldown." - " For uv-style cooldown control, install `pipx[uv]` and set" - " `UV_NO_CONFIG=1` in your shell." - ) return None diff --git a/tests/docs/test_widgets.py b/tests/docs/test_widgets.py index a1cc579..b9e1b44 100644 --- a/tests/docs/test_widgets.py +++ b/tests/docs/test_widgets.py @@ -143,7 +143,7 @@ def test_body_for_uvx_bypass_inserts_no_config_flag() -> None: def test_body_for_pipx_bypass_returns_caveat_note() -> None: - """Pipx bypass renders identically to off and surfaces a no-op note.""" + """Pipx bypass renders identically to off and surfaces a caveat note.""" client = CLIENTS[0] pipx = next(m for m in METHODS if m.id == "pipx") body_off, _, note_off = _body_for(client, pipx, client.scopes[0], _OFF) @@ -151,11 +151,12 @@ def test_body_for_pipx_bypass_returns_caveat_note() -> None: assert body_off == body_bp # same command emitted assert note_off is None assert note_bp is not None - assert "pipx" in note_bp.lower() + # Note redirects users to the uvx tab for true cooldown support. + assert "uvx" in note_bp.lower() def test_body_for_pip_bypass_returns_caveat_note() -> None: - """Pip bypass renders identically to off and surfaces a no-op note.""" + """Pip bypass renders identically to off and surfaces a caveat note.""" client = CLIENTS[0] pip = next(m for m in METHODS if m.id == "pip") body_off, _, note_off = _body_for(client, pip, client.scopes[0], _OFF) @@ -166,13 +167,37 @@ def test_body_for_pip_bypass_returns_caveat_note() -> None: assert "pip" in note_bp.lower() -def test_body_for_pipx_days_uses_pip_args_form_with_absolute_date() -> None: - """Pipx days uses absolute date (pipx 1.8.0's bundled pip rejects PD).""" +def test_body_for_pipx_days_falls_back_to_bare_run() -> None: + """Pipx days renders identically to off (no per-package cooldown override). + + pipx's pip backend has no per-package ``--uploaded-prior-to`` flag, + so a days-mode cutoff would filter fresh ``libtmux-mcp`` releases + out of the resolver. The caveat note redirects users to the uvx tab. + """ client = CLIENTS[0] pipx = next(m for m in METHODS if m.id == "pipx") - body, language, _ = _body_for(client, pipx, client.scopes[0], _DAYS) - assert "--pip-args=--uploaded-prior-to=" in body + body_off, _, _ = _body_for(client, pipx, client.scopes[0], _OFF) + body_days, language, note = _body_for(client, pipx, client.scopes[0], _DAYS) + assert body_days == body_off # bare command, no cooldown flag + assert "--pip-args" not in body_days + assert "--uploaded-prior-to" not in body_days + assert "" not in body_days assert language == "console" + assert note is not None + assert "uvx" in note.lower() + + +def test_body_for_pip_days_falls_back_to_bare_install() -> None: + """Pip days renders identically to off (same per-package-override limit).""" + client = CLIENTS[0] + pip = next(m for m in METHODS if m.id == "pip") + body_off, _, _ = _body_for(client, pip, client.scopes[0], _OFF) + body_days, _, note = _body_for(client, pip, client.scopes[0], _DAYS) + assert body_days == body_off + assert "--uploaded-prior-to" not in body_days + assert "" not in body_days + assert note is not None + assert "uvx" in note.lower() def test_body_for_json_client_off_returns_config_snippet() -> None: @@ -242,8 +267,14 @@ def test_body_for_gemini_user_inserts_scope_flag() -> None: assert language == "console" -def test_pip_panel_has_cooldown_aware_pip_prereq() -> None: - """Panel.pip_prereq is set only for the pip method, with cooldown applied.""" +def test_pip_panel_has_bare_pip_prereq_across_modes() -> None: + """Pip panels emit the same bare ``pip install`` line across modes. + + pip's ``--uploaded-prior-to`` has no per-package override (so a + days-mode cutoff would filter fresh ``libtmux-mcp`` releases out of + the resolver). All three modes fall back to the bare install line; + the per-panel cooldown note redirects users to the uvx snippet. + """ panels = build_panels() pip_panels = [p for p in panels if p.method.id == "pip"] non_pip = [p for p in panels if p.method.id != "pip"] @@ -251,10 +282,12 @@ def test_pip_panel_has_cooldown_aware_pip_prereq() -> None: assert all(p.pip_prereq is not None for p in pip_panels) days_pip = next(p for p in pip_panels if p.cooldown.id == "days") off_pip = next(p for p in pip_panels if p.cooldown.id == "off") - assert days_pip.pip_prereq is not None - assert off_pip.pip_prereq is not None - assert "--uploaded-prior-to " in days_pip.pip_prereq - assert "--uploaded-prior-to" not in off_pip.pip_prereq + bypass_pip = next(p for p in pip_panels if p.cooldown.id == "bypass") + days_prereq = days_pip.pip_prereq + assert days_prereq is not None + assert days_prereq == off_pip.pip_prereq == bypass_pip.pip_prereq + assert "--uploaded-prior-to" not in days_prereq + assert "" not in days_prereq def test_body_for_unknown_kind_raises() -> None: @@ -347,10 +380,10 @@ def test_cooldown_days_slot_filter_registered_on_widget_render( ) app = _build(make_app, real_widget_srcdir) html = (pathlib.Path(app.outdir) / "index.html").read_text(encoding="utf-8") - # uvx + pip days bodies emit the duration slot; pipx days bodies - # emit the date slot. Both kinds must appear in any complete build. + # uvx days bodies emit the duration slot. pipx and pip days bodies + # fall back to the bare command (pip has no per-package cooldown + # override) so no date slot appears in any rendered snippet. assert "data-cooldown-duration-slot" in html - assert "data-cooldown-date-slot" in html # And no raw sentinel escapes to the rendered page. assert "<COOLDOWN_DURATION>" not in html assert "<COOLDOWN_DATE>" not in html