Resolve install binary names through plugins

This commit is contained in:
Nick Sweeting 2026-07-28 20:22:50 -07:00
parent 3d680b5379
commit a3ca0f68c0
No known key found for this signature in database
12 changed files with 110 additions and 53 deletions

View File

@ -46,7 +46,7 @@ jobs:
- name: Setup Python and uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
enable-cache: false
- name: Install ArchiveBox and screenshot dependencies
run: uv sync --no-cache --frozen

View File

@ -49,7 +49,7 @@ jobs:
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Resolve Docker through abxpkg

View File

@ -26,7 +26,7 @@ jobs:
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Install ArchiveBox development dependencies
run: uv sync --no-cache --locked --dev
@ -81,7 +81,7 @@ jobs:
if: ${{ !contains(fromJSON('["freebsd", "openbsd"]'), matrix.environment) }}
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Install ArchiveBox
if: ${{ !contains(fromJSON('["freebsd", "openbsd"]'), matrix.environment) }}

View File

@ -25,11 +25,11 @@ jobs:
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Verify checked dependency lock
run: uv lock --check
run: uv lock --no-cache --check
- name: Install dependencies with uv
run: uv sync --no-cache --locked --dev --extra sonic --extra debug

View File

@ -23,13 +23,13 @@ jobs:
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Build every distribution
run: |
set -Eeuo pipefail
uv build --all-packages --no-sources --out-dir dist --clear
uv build --no-cache --all-packages --no-sources --out-dir dist --clear
shopt -s nullglob
artifacts=(dist/*)
@ -92,7 +92,7 @@ jobs:
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Download distributions

View File

@ -30,7 +30,7 @@ jobs:
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Resolve discovery tools through abxpkg
@ -96,7 +96,7 @@ jobs:
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Resolve optional build dependencies through abxpkg

View File

@ -46,7 +46,7 @@ jobs:
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
version: "0.11.3"
enable-cache: true
enable-cache: false
cache-dependency-glob: uv.lock
- name: Prepare abxpkg environment

View File

@ -12,6 +12,63 @@ from rich import print
from archivebox.misc.util import docstring, enforce_types
def _resolve_install_plugin_names(
requested_names: tuple[str, ...],
) -> list[str]:
"""Resolve `archivebox install <name>` tokens to installable plugin names."""
from archivebox.config import CONSTANTS
from archivebox.config.common import get_config, normalize_runtime_config
from archivebox.machine.models import Machine
from abx_dl.config import get_required_binary_requests
from abx_dl.models import discover_plugins, filter_plugins
plugins = discover_plugins(runtime="archivebox")
config = get_config(include_machine=False)
runtime_config = normalize_runtime_config(config.for_crawl(), json_safe=False)
derived_config = normalize_runtime_config(Machine.current().config, json_safe=False)
selected_plugins = dict(filter_plugins(plugins, list(requested_names), include_providers=True))
unmatched_names: list[str] = []
for requested_name in requested_names:
if requested_name in selected_plugins:
continue
matched_plugin_names: list[str] = []
for plugin_name, plugin in plugins.items():
logical_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
)
actual_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
logical_names=False,
)
for logical_record, actual_record in zip(logical_records, actual_records, strict=False):
logical_name = str(logical_record["name"])
actual_name = str(actual_record["name"])
display_name = Path(actual_name).expanduser().name if ("/" in actual_name or actual_name.startswith("~")) else actual_name
if requested_name in {logical_name, actual_name, display_name}:
matched_plugin_names.append(plugin_name)
break
if matched_plugin_names:
selected_plugins.update(filter_plugins(plugins, matched_plugin_names, include_providers=True))
else:
unmatched_names.append(requested_name)
if unmatched_names:
raise click.UsageError(f"No plugins or required binaries found matching: {', '.join(unmatched_names)}")
return sorted(selected_plugins)
def ensure_data_dir_lib_symlink(data_dir: Path, abxpkg_lib_dir: Path) -> Path | None:
"""Expose the shared abxpkg lib dir at DATA_DIR/lib after install."""
link_path = data_dir / "lib"
@ -90,7 +147,7 @@ def install(binaries: tuple[str, ...] = (), binproviders: str = "*", dry_run: bo
setup_django()
plugin_names = list(binaries)
plugin_names = _resolve_install_plugin_names(binaries) if binaries else []
if binproviders != "*":
plugin_names.extend(provider.strip() for provider in binproviders.split(",") if provider.strip())

View File

@ -27,7 +27,7 @@ CAPTURE_PROFILES=$'desktop|1600|1000\ntablet|1024|1366\nmobile|390|844'
stop_background_runner() {
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from archivebox.workers.supervisord_util import get_existing_supervisord_process, stop_worker; supervisor=get_existing_supervisord_process(quiet=True); supervisor is not None and stop_worker(supervisor, "worker_runner")'
) >/dev/null
}
@ -79,7 +79,7 @@ cleanup() {
if [[ "$CREATED_TEMP_USER" == "1" && -f "$DATA_DIR/index.sqlite3" ]]; then
(
cd "$DATA_DIR"
UI_SCREENSHOT_USERNAME="$USERNAME" uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
UI_SCREENSHOT_USERNAME="$USERNAME" uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'import os; from django.contrib.auth import get_user_model; get_user_model().objects.filter(username=os.environ["UI_SCREENSHOT_USERNAME"]).delete()'
) >/dev/null 2>&1 || true
fi
@ -91,12 +91,12 @@ mkdir -p "$DATA_DIR" "$OUTPUT_DIR" "$PUBLIC_OUTPUT_DIR"
echo "[*] Initializing the ArchiveBox collection at $DATA_DIR"
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox init --quick
uv run --no-cache --project "$REPO_DIR" archivebox init --quick
)
SEED_STATE="$( (
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from django.db.models import Count; from archivebox.core.models import Snapshot, ArchiveResult; from archivebox.crawls.models import CrawlSchedule; from archivebox.personas.models import Persona; from archivebox.api.models import APIToken; from signal_webhooks.utils import get_webhook_model; recent=list(Snapshot.objects.filter(status=Snapshot.StatusChoices.SEALED).order_by("-bookmarked_at").values_list("id", "status")[:100]); counts=dict(ArchiveResult.objects.filter(snapshot_id__in=[row[0] for row in recent], status="succeeded").values_list("snapshot_id").annotate(Count("id"))); screenshots=set(ArchiveResult.objects.filter(snapshot_id__in=[row[0] for row in recent], plugin="screenshot", status="succeeded").values_list("snapshot_id", flat=True)); useful=sum(counts.get(snapshot_id,0)>=8 and snapshot_id in screenshots for snapshot_id,status in recent)>=2; print(int(useful)); print(CrawlSchedule.objects.count()); print(Persona.objects.exclude(name="Default").count()); print(APIToken.objects.count()); print(get_webhook_model().objects.count())'
) | tail -5)"
HAS_USEFUL_SNAPSHOT="$(printf '%s\n' "$SEED_STATE" | sed -n '1p')"
@ -109,7 +109,7 @@ if [[ "$HAS_USEFUL_SNAPSHOT" == "0" ]]; then
echo "[*] Archiving real reference sites so snapshot views have meaningful outputs"
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox add \
uv run --no-cache --project "$REPO_DIR" archivebox add \
--depth=0 \
--overwrite \
--tag=documentation,reference \
@ -122,7 +122,7 @@ if [[ "$HAS_SCHEDULE" == "0" ]]; then
echo "[*] Creating a real weekly documentation crawl schedule"
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox schedule --every=weekly --depth=0 --tag=documentation https://archivebox.io/feed.xml
uv run --no-cache --project "$REPO_DIR" archivebox schedule --every=weekly --depth=0 --tag=documentation https://archivebox.io/feed.xml
)
fi
@ -130,7 +130,7 @@ if [[ "$HAS_PERSONA" == "0" ]]; then
echo "[*] Creating a real browser persona for the populated Persona views"
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox persona create "Research Browser"
uv run --no-cache --project "$REPO_DIR" archivebox persona create "Research Browser"
)
fi
@ -139,7 +139,7 @@ fi
ROUTE_CONFIG="$( (
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from urllib.parse import urlparse; from archivebox.config.common import get_config; from archivebox.core.routes_util import get_admin_base_url, get_web_base_url; config = get_config(); admin = get_admin_base_url(config=config); web = get_web_base_url(config=config); parsed = urlparse(admin); print(admin); print(web); print(parsed.port or (443 if parsed.scheme == "https" else 80))'
) | tail -3)"
ADMIN_BASE_URL="$(printf '%s\n' "$ROUTE_CONFIG" | sed -n '1p')"
@ -155,10 +155,10 @@ fi
echo "[*] Creating a temporary screenshot admin"
(
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from django.contrib.auth import get_user_model; get_user_model().objects.filter(username__startswith="archivebox-screenshots-").delete()'
UI_SCREENSHOT_USERNAME="$USERNAME" UI_SCREENSHOT_PASSWORD="$PASSWORD" \
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'import os; from django.contrib.auth import get_user_model; get_user_model().objects.create_superuser(username=os.environ["UI_SCREENSHOT_USERNAME"], password=os.environ["UI_SCREENSHOT_PASSWORD"])'
)
CREATED_TEMP_USER=1
@ -169,7 +169,7 @@ echo "[*] Starting ArchiveBox on port $PORT"
# Use the real server command so the runner, worker, and log views describe
# the same persistent runtime that an operator sees.
UI_SCREENSHOT_HIDE_HIGH_LOAD_WARNING=1 \
exec uv run --project "$REPO_DIR" archivebox server "127.0.0.1:$PORT"
exec uv run --no-cache --project "$REPO_DIR" archivebox server "127.0.0.1:$PORT"
) >"$DATA_DIR/ui-screenshot-server.log" 2>&1 &
SERVER_PID=$!
@ -265,7 +265,7 @@ while [[ "$capture_index" -lt "${#VIEWS[@]}" ]]; do
fi
elif [[ "$profile" == "desktop" || "$capture_mode" == wait-replay:* || -z "${ABXPKG_LIB_DIR:-}" ]]; then
capture_log="$CAPTURE_ROOT/$(printf '%02d' "$capture_index")-$profile-abx-dl.log"
if ! env "${capture_env[@]}" uv run --project "$REPO_DIR" abx-dl dl \
if ! env "${capture_env[@]}" uv run --no-cache --project "$REPO_DIR" abx-dl dl \
--plugins=screenshot \
--timeout=120 \
--dir "$capture_dir" \
@ -281,7 +281,7 @@ while [[ "$capture_index" -lt "${#VIEWS[@]}" ]]; do
echo "[!] Screenshot navigation metadata is missing for $profile $url" >&2
exit 1
fi
uv run --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" validate \
uv run --no-cache --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" validate \
"$screenshot_metadata_path" "$expected_path"
else
screenshot_path="$capture_dir/screenshot.png"
@ -300,7 +300,7 @@ while [[ "$capture_index" -lt "${#VIEWS[@]}" ]]; do
SCREENSHOT_VARIANTS_JSON="$responsive_variants" \
SCREENSHOT_COLLAPSE_FILTERS=1 \
node "$REPO_DIR/bin/take_screenshot.js" "$url" "$screenshot_path" >"$capture_dir/report.json"
uv run --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" validate \
uv run --no-cache --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" validate \
"$capture_dir/report.json" "$expected_path"
fi
fi
@ -312,14 +312,14 @@ while [[ "$capture_index" -lt "${#VIEWS[@]}" ]]; do
cp "$screenshot_path" "$PUBLIC_OUTPUT_DIR/$filename"
UI_SCREENSHOT_NAME="$name" UI_SCREENSHOT_URL="$url" UI_SCREENSHOT_SOURCE="$source" \
UI_SCREENSHOT_FILENAME="$filename" UI_SCREENSHOT_PROFILE="$profile" \
uv run --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" append \
uv run --no-cache --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" append \
"$MANIFEST_FILE" "$OUTPUT_DIR/$filename"
done <<<"$CAPTURE_PROFILES"
# Capture the public views before login because the real admin-login hint
# intentionally redirects authenticated personas away from /public/.
if [[ "$capture_index" == "2" ]]; then
ABXPKG_LIB_DIR="$(uv run --project "$REPO_DIR" abx-dl config --get ABXPKG_LIB_DIR | sed 's/^[^=]*=//; s/^"//; s/"$//')"
ABXPKG_LIB_DIR="$(uv run --no-cache --project "$REPO_DIR" abx-dl config --get ABXPKG_LIB_DIR | sed 's/^[^=]*=//; s/^"//; s/"$//')"
SCREENSHOT_CHROME_BINARY="$ABXPKG_LIB_DIR/env/bin/chromium"
if [[ ! -x "$SCREENSHOT_CHROME_BINARY" ]]; then
echo "[!] abx-dl projected Chromium was not found at $SCREENSHOT_CHROME_BINARY" >&2
@ -344,7 +344,7 @@ while [[ "$capture_index" -lt "${#VIEWS[@]}" ]]; do
CREATE_WEBHOOK="$CREATE_WEBHOOK" \
node "$REPO_DIR/bin/setup_ui_screenshot_data.js" "$ADMIN_BASE_URL" "$USERNAME"
OPENCODE_PORT="$(uv run --project "$REPO_DIR" python - <<'PY'
OPENCODE_PORT="$(uv run --no-cache --project "$REPO_DIR" python - <<'PY'
import socket
with socket.socket() as sock:
@ -354,20 +354,20 @@ PY
)"
(
cd "$DATA_DIR"
OPENCODE_PORT="$OPENCODE_PORT" uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
OPENCODE_PORT="$OPENCODE_PORT" uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'import os; from archivebox.machine.models import Machine; Machine.from_json({"config": {"OPENCODE_ENABLED": True, "OPENCODE_PORT": int(os.environ["OPENCODE_PORT"])}})'
uv run --project "$REPO_DIR" archivebox install opencode --binproviders=env,pnpm
uv run --no-cache --project "$REPO_DIR" archivebox install opencode --binproviders=env,pnpm
)
SWEETING_CAPTURE_STARTED_AT="$( (
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from django.utils import timezone; print(timezone.now().isoformat())'
) | tail -1)"
echo "[*] Starting a real Sweeting.me capture for the live progress view"
(
cd "$DATA_DIR"
exec uv run --project "$REPO_DIR" archivebox add \
exec uv run --no-cache --project "$REPO_DIR" archivebox add \
--depth=0 \
--overwrite \
--tag=screenshot-gallery \
@ -379,7 +379,7 @@ PY
for _attempt in $(seq 1 120); do
LIVE_SNAPSHOT_RECORD="$( (
cd "$DATA_DIR"
UI_SCREENSHOT_CAPTURE_STARTED_AT="$SWEETING_CAPTURE_STARTED_AT" uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
UI_SCREENSHOT_CAPTURE_STARTED_AT="$SWEETING_CAPTURE_STARTED_AT" uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'import os; from django.utils.dateparse import parse_datetime; from archivebox.core.models import Snapshot; from archivebox.core.routes_util import build_snapshot_url; started_at=parse_datetime(os.environ["UI_SCREENSHOT_CAPTURE_STARTED_AT"]); snapshot=Snapshot.objects.filter(url__startswith="https://sweeting.me",bookmarked_at__gte=started_at).order_by("-bookmarked_at").first(); print(build_snapshot_url(str(snapshot.id), "") if snapshot else "")'
) | tail -1)"
if [[ -n "$LIVE_SNAPSHOT_RECORD" ]]; then
@ -403,7 +403,7 @@ PY
RECORD_CONFIG="$( (
cd "$DATA_DIR"
uv run --project "$REPO_DIR" archivebox manage shell --no-imports -c \
uv run --no-cache --project "$REPO_DIR" archivebox manage shell --no-imports -c \
'from django.db.models import Count; from archivebox.core.models import Snapshot, ArchiveResult, Tag; from archivebox.crawls.models import Crawl, CrawlSchedule; from archivebox.personas.models import Persona; from archivebox.machine.models import Machine, NetworkInterface, Binary, Process; from archivebox.api.models import APIToken; from django.contrib.auth import get_user_model; from signal_webhooks.utils import get_webhook_model; from archivebox.core.routes_util import build_snapshot_url; recent=list(Snapshot.objects.filter(status=Snapshot.StatusChoices.SEALED).order_by("-bookmarked_at").values_list("id", flat=True)[:1000]); counts=dict(ArchiveResult.objects.filter(snapshot_id__in=recent,status="succeeded").values_list("snapshot_id").annotate(Count("id"))); snapshot_id=str(max(recent,key=lambda item: counts.get(item,0))); snapshot=Snapshot.objects.get(id=snapshot_id); result=ArchiveResult.objects.filter(snapshot_id=snapshot_id,status="succeeded").order_by("-output_size").first() or ArchiveResult.objects.filter(snapshot_id=snapshot_id).first(); tag=snapshot.tags.first() or Tag.objects.first(); crawl=snapshot.crawl or Crawl.objects.order_by("-created_at").first(); schedule=CrawlSchedule.objects.order_by("-created_at").first(); persona=Persona.objects.exclude(name="Default").order_by("-created_at").first() or Persona.objects.first(); machine=Machine.objects.order_by("-modified_at").first(); interface=NetworkInterface.objects.order_by("-modified_at").first(); binary=Binary.objects.order_by("-modified_at").first(); process=Process.objects.order_by("-created_at").first(); token=APIToken.objects.order_by("-created_at").first(); webhook=get_webhook_model().objects.order_by("-created_at").first(); user=get_user_model().objects.get(username="'"$USERNAME"'"); values={"SNAPSHOT_ID":snapshot_id,"SNAPSHOT_VIEW_URL":build_snapshot_url(snapshot_id,""),"SNAPSHOT_FILES_URL":build_snapshot_url(snapshot_id,"/?files=1"),"ARCHIVERESULT_ID":str(result.id),"TAG_ID":str(tag.id),"USER_ID":str(user.id),"CRAWL_ID":str(crawl.id),"SCHEDULE_ID":str(schedule.id),"PERSONA_ID":str(persona.id),"MACHINE_ID":str(machine.id),"INTERFACE_ID":str(interface.id),"BINARY_ID":str(binary.id),"PROCESS_ID":str(process.id),"TOKEN_ID":str(token.id),"WEBHOOK_ID":str(webhook.id)}; [print(f"{key}={value}") for key,value in values.items()]'
) | tail -15)"
eval "$RECORD_CONFIG"
@ -474,7 +474,7 @@ PY
SCREENSHOT_WIDTH=1600 \
SCREENSHOT_HEIGHT=1000 \
node "$REPO_DIR/bin/take_screenshot.js" "$LIVE_SNAPSHOT_VIEW_URL" "$CAPTURE_ROOT/snapshot-output-discovery.png" >"$SNAPSHOT_DISCOVERY_REPORT"
SNAPSHOT_OUTPUT_PLUGINS="$(UI_SCREENSHOT_DISCOVERY_REPORT="$SNAPSHOT_DISCOVERY_REPORT" uv run --project "$REPO_DIR" python -c \
SNAPSHOT_OUTPUT_PLUGINS="$(UI_SCREENSHOT_DISCOVERY_REPORT="$SNAPSHOT_DISCOVERY_REPORT" uv run --no-cache --project "$REPO_DIR" python -c \
'import json, os; from urllib.parse import urlsplit; report=json.load(open(os.environ["UI_SCREENSHOT_DISCOVERY_REPORT"])); print("\n".join("{}\t{}".format(output["plugin"], "wait-replay:Nick Sweeting" if urlsplit(output["previewUrl"]).path.endswith(".wacz") else "") for output in report["checks"]["snapshotOutputs"]))')"
if [[ -z "$SNAPSHOT_OUTPUT_PLUGINS" ]]; then
echo "[!] The Sweeting.me snapshot detail page exposed no selectable outputs" >&2
@ -492,7 +492,7 @@ PY
done
[[ "$MAX_VIEWS" != "0" ]] && export UI_SCREENSHOT_ALLOW_PARTIAL=1
uv run --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" build \
uv run --no-cache --project "$REPO_DIR" "$REPO_DIR/bin/generate_ui_screenshot_gallery.py" build \
"$MANIFEST_FILE" "$REPO_DIR/docs/Screenshots.md" "$PUBLIC_OUTPUT_DIR/index.html"
echo "[+] Captured $capture_index views at desktop, tablet, and mobile sizes"

View File

@ -1,6 +1,6 @@
{
"name": "archivebox",
"version": "0.9.35rc163",
"version": "0.9.35rc164",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {

View File

@ -1,6 +1,6 @@
[project]
name = "archivebox"
version = "0.9.35rc163"
version = "0.9.35rc164"
requires-python = ">=3.13"
description = "Self-hosted internet archiving solution."
authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}]
@ -83,7 +83,7 @@ dependencies = [
"abxbus==2.5.40", # EventBus API
"abxpkg", # direct imports only; version constrained by abx-dl -> abx-plugins
"abx-plugins", # direct imports only; version constrained by abx-dl
"abx-dl==1.12.29", # shared ArchiveBox downloader package
"abx-dl==1.12.31", # shared ArchiveBox downloader package
### UUID7 backport for Python <3.14
"uuid7>=0.1.0; python_version < '3.14'", # provides the uuid_extensions module on Python 3.13
]
@ -348,7 +348,7 @@ Donate = "https://github.com/ArchiveBox/ArchiveBox/wiki/Donations"
[tool.bumpver]
current_version = "v0.9.35rc163"
current_version = "v0.9.35rc164"
version_pattern = "vMAJOR.MINOR.PATCH[PYTAGNUM]"
commit_message = "bump version {old_version} -> {new_version}"
tag_message = "{new_version}"

22
uv.lock
View File

@ -24,7 +24,7 @@ abxpkg = "2100-01-01T00:00:00Z"
[[package]]
name = "abx-dl"
version = "1.12.29"
version = "1.12.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "abx-plugins" },
@ -39,14 +39,14 @@ dependencies = [
{ name = "rich" },
{ name = "rich-click" },
]
sdist = { url = "https://files.pythonhosted.org/packages/27/1c/ef4512b5209a94f3e3945907ae3fbefc47ff2b630aff9f7d957cfb8917da/abx_dl-1.12.29.tar.gz", hash = "sha256:d072755cb069ae8f412316810ab38a7c754e03555d02e0d9a8eb5ae04a8b0d60", size = 86814, upload-time = "2026-07-28T17:52:24.237Z" }
sdist = { url = "https://files.pythonhosted.org/packages/83/12/a17dbf6ff8cd8a17ecbbd8b2c1b5ea47d2562299d9efd7bc7ba00b38d623/abx_dl-1.12.31.tar.gz", hash = "sha256:544a8ac11a08b390c021a92bfb7d648f85a3b110ed728864c6c6f798333b39d0", size = 86789, upload-time = "2026-07-29T03:20:31.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/ea/9c15aeacf2492004bca3874008dad34557e06c56ae6c6caf037c068b93a9/abx_dl-1.12.29-py3-none-any.whl", hash = "sha256:429da9b175680ead21cae69ebf3e22204ad16a48c0e656d65d82b9153014caf7", size = 90704, upload-time = "2026-07-28T17:52:23.154Z" },
{ url = "https://files.pythonhosted.org/packages/da/9d/2c02fbea7239f0aac9857c0670328b40ee16e5491ebd3fe802e2cb56a7a8/abx_dl-1.12.31-py3-none-any.whl", hash = "sha256:41c389573c05c6db47bb93ae0d45876cdbc22468dbf075204546d6da8eb23579", size = 90708, upload-time = "2026-07-29T03:20:29.935Z" },
]
[[package]]
name = "abx-plugins"
version = "1.12.32"
version = "1.12.39"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "abxbus" },
@ -56,9 +56,9 @@ dependencies = [
{ name = "rich-click" },
{ name = "uv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/08/0f0150ce87f2ed6d7bf946f0f5bb075d8dd7dc5ff2e42c92691ce62231b6/abx_plugins-1.12.32.tar.gz", hash = "sha256:24d18831da390fe88a81c239ebaf22b500cc36e6ebfd6f375a57824b6cc8c334", size = 260578, upload-time = "2026-07-28T15:20:39.477Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/2c/63433217fd2ae742cc81d00acd3585a714b1439b9916ea0dec3d25831f1a/abx_plugins-1.12.39.tar.gz", hash = "sha256:596bf68fd994364dd37bb4f2830dfe11ef0e676f9880ac44e278ea4f5fb0b964", size = 260565, upload-time = "2026-07-29T02:47:16.992Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/06/0d/3cca76c0f94a883e3b619180b80232319932474448c0b38c633dd90b255d/abx_plugins-1.12.32-py3-none-any.whl", hash = "sha256:721d921554d4cacc6c756b737735b315ff7e74921ee1ce303d37f4ea727f05ba", size = 411230, upload-time = "2026-07-28T15:20:38.194Z" },
{ url = "https://files.pythonhosted.org/packages/dd/5f/a7f3b33db738b52e96d16768cc6bbccaa230497b7f446cf3aa94201c66c9/abx_plugins-1.12.39-py3-none-any.whl", hash = "sha256:15d54449c7a0e534152ddb71bdb7fa83b0fa01f5883c52f5c52ead8a8dea5c74", size = 411239, upload-time = "2026-07-29T02:47:15.565Z" },
]
[[package]]
@ -77,7 +77,7 @@ wheels = [
[[package]]
name = "abxpkg"
version = "1.12.25"
version = "1.12.26"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pip" },
@ -86,9 +86,9 @@ dependencies = [
{ name = "rich-click" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/e1ccdb0cee3bc18e33d00a6484273887836db54e96c75e5a2244287de97f/abxpkg-1.12.25.tar.gz", hash = "sha256:2cac56e9d624565c8b175d5f5469147ab7a756c0092b9bd13ee52ba94ae89287", size = 220914, upload-time = "2026-07-28T09:35:55.827Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/aa/ba5ad1fea91287a05eb6d563bd4bc8904104824e5429865671a4610d200d/abxpkg-1.12.26.tar.gz", hash = "sha256:fde0d3eaf854af21acc296a1aceeb51cae439fbd16d162183c774121f84e0a9a", size = 221167, upload-time = "2026-07-29T01:52:10.984Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/e1/80657dc861e6f73444579915f54994a4df86f940ab9f7fa447eb2756d569/abxpkg-1.12.25-py3-none-any.whl", hash = "sha256:d1580766c63ff7cd67209ce6bb3c61c6efcb5ae65fa332d482fca44f5f1dfbd4", size = 234632, upload-time = "2026-07-28T09:35:54.448Z" },
{ url = "https://files.pythonhosted.org/packages/76/2f/c3befcb4446a24f8ad22c51feae972cadf67cb5be4e169b809580a7c73c7/abxpkg-1.12.26-py3-none-any.whl", hash = "sha256:b3f11b4ceefebf8651311b59b5b34048eb9dfcfa090b735ec81da4264de13d54", size = 234868, upload-time = "2026-07-29T01:52:11.977Z" },
]
[[package]]
@ -123,7 +123,7 @@ wheels = [
[[package]]
name = "archivebox"
version = "0.9.35rc163"
version = "0.9.35rc164"
source = { editable = "." }
dependencies = [
{ name = "abx-dl" },
@ -223,7 +223,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "abx-dl", specifier = "==1.12.29" },
{ name = "abx-dl", specifier = "==1.12.31" },
{ name = "abx-plugins" },
{ name = "abxbus", specifier = "==2.5.40" },
{ name = "abxpkg" },