Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion abx_plugins/plugins/claudecode/claudecode_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ def run_claude_code(
Returns: (stdout, stderr, returncode)
"""
config = load_config(Path(__file__).with_name("config.json"))
binary = str(config.CLAUDECODE_BINARY)
binary = str(os.environ.get("CLAUDECODE_BINARY") or config.CLAUDECODE_BINARY)
if not Path(binary).exists():
return "", f"Claude Code binary not found: {binary}", 1

cmd = [binary]

Expand Down
58 changes: 53 additions & 5 deletions abx_plugins/plugins/ytdlp/templates/card.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
<!-- YT-DLP output list -->
{% if media_files %}
<div class="loose-items" style="pointer-events: auto;">
<div class="ytdlp-media-card" style="pointer-events: auto;">
{% for file in media_files %}
<a href="{{ file.url|default:file.path|urlencode }}" target="preview"
title="{{ file.name }}">
📄 {{ file.name }}
</a>
{% if file.is_browser_playable %}
<div class="ytdlp-media-item" style="margin-bottom: 6px;">
<button
type="button"
class="ytdlp-load-player"
data-src="{{ file.url|default:file.path|urlencode }}"
data-media-type="{{ file.media_type }}"
data-name="{{ file.name }}"
style="cursor: pointer; width: 100%; text-align: left; border: 1px solid #ddd; border-radius: 4px; background: #f8f8f8; padding: 6px 8px;"
>
{% if file.is_video %}🎬{% else %}🎧{% endif %} Play {{ file.name }}
</button>
<a href="{{ file.url|default:file.path|urlencode }}" target="preview" title="{{ file.name }}">
Download file
</a>
</div>
{% else %}
<div class="loose-items" style="pointer-events: auto;">
<a href="{{ file.url|default:file.path|urlencode }}" target="preview" title="{{ file.name }}">
{% if file.is_video %}🎬{% elif file.is_audio %}🎧{% else %}📄{% endif %} {{ file.name }}
</a>
</div>
{% endif %}
{% endfor %}
</div>
<script>
(function () {
if (window.__archiveboxYtdlpCardLoader) return;
window.__archiveboxYtdlpCardLoader = true;

document.addEventListener("click", function (event) {
var button = event.target.closest && event.target.closest(".ytdlp-load-player");
if (!button) return;

var src = button.getAttribute("data-src");
var mediaType = button.getAttribute("data-media-type");
var name = button.getAttribute("data-name") || "archived media";
if (!src) return;

var media = document.createElement(mediaType === "audio" ? "audio" : "video");
media.setAttribute("controls", "controls");
media.setAttribute("preload", "none");
media.setAttribute("data-no-preview", "1");
media.setAttribute("aria-label", name);
media.src = src;
media.style.width = "100%";
media.style.maxHeight = mediaType === "audio" ? "" : "220px";
media.style.background = "#000";
media.style.display = "block";

button.replaceWith(media);
});
}());
</script>
{% else %}
<div class="thumbnail-compact" data-plugin="ytdlp" data-compact="1">
<span class="thumbnail-compact-icon">🎬</span>
Expand Down
21 changes: 21 additions & 0 deletions abx_plugins/plugins/ytdlp/tests/test_ytdlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,27 @@ def test_hook_script_exists():
assert YTDLP_HOOK.exists(), f"Hook not found: {YTDLP_HOOK}"


def test_card_template_loads_browser_media_on_click():
"""Card template should not fetch archived media until the user asks to play it."""
template = (PLUGIN_DIR / "templates" / "card.html").read_text()

assert "ytdlp-load-player" in template
assert 'data-src="{{ file.url|default:file.path|urlencode }}"' in template
assert "media.src = src" in template
assert "<video" not in template
assert "<audio" not in template


def test_card_template_links_non_browser_media_without_player():
"""Non-browser-playable yt-dlp outputs should stay as regular file links."""
template = (PLUGIN_DIR / "templates" / "card.html").read_text()

assert "{% if file.is_browser_playable %}" in template
assert "{% else %}" in template
assert "Download file" in template
assert 'href="{{ file.url|default:file.path|urlencode }}"' in template


def test_verify_deps_with_abxpkg():
"""Verify yt-dlp resolves through the real dependency preflight."""
binary_path = require_ytdlp_binary()
Expand Down
21 changes: 10 additions & 11 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,13 @@ def ensure_claude_code_prereqs(tmp_path_factory):
live Anthropic credentials are unavailable.
"""

api_key = os.environ.get("ANTHROPIC_API_KEY", "")
if not api_key:
pytest.skip(
"ANTHROPIC_API_KEY not set. Claude Code integration tests "
"require live Anthropic credentials.",
)

def install_claude_code_with_abxpkg() -> str:
from abx_plugins.plugins.chrome.tests.chrome_test_helpers import get_test_env
from abx_plugins.plugins.base.utils import load_required_binary
Expand Down Expand Up @@ -336,14 +343,6 @@ def install_claude_code_with_abxpkg() -> str:
elif not Path(claude_bin).exists():
pytest.fail(f"CLAUDECODE_BINARY is set but does not exist: {claude_bin}")

# Check API key
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
if not api_key:
pytest.fail(
"ANTHROPIC_API_KEY not set. Claude Code integration tests "
"require a valid API key.",
)

# Quick smoke test: claude --version
result = subprocess.run(
[claude_bin, "--version"],
Expand All @@ -367,8 +366,8 @@ def ensure_anthropic_api_key():
"""
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
if not api_key:
pytest.fail(
"ANTHROPIC_API_KEY not set. Integration tests that call the "
"Anthropic API require a valid API key.",
pytest.skip(
"ANTHROPIC_API_KEY not set. Integration tests that call the "
"Anthropic API require live credentials.",
)
return api_key