Iterative Retrieval
affaan-m/ECC
Pattern for progressively refining context retrieval to solve the subagent context problem.
Web archiving and retrieval via Wayback Machine and Archive.today.
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jamditis/claude-skills-journalism web-archiving --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .claude/skills && cp -r skills-src/research-toolkit/skills/web-archiving .claude/skills/web-archiving && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .claude/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archivingType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jamditis/claude-skills-journalism web-archiving --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .agents/skills && cp -r skills-src/research-toolkit/skills/web-archiving .agents/skills/web-archiving && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .agents/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jamditis/claude-skills-journalism web-archiving --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/research-toolkit/skills/web-archiving .cursor/skills/web-archiving && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .cursor/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/jamditis/claude-skills-journalism.git --path research-toolkit/skills/web-archiving--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jamditis/claude-skills-journalism web-archiving --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/research-toolkit/skills/web-archiving .gemini/skills/web-archiving && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .gemini/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jamditis/claude-skills-journalism web-archivingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .github/skills && cp -r skills-src/research-toolkit/skills/web-archiving .github/skills/web-archiving && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .github/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jamditis/claude-skills-journalism --skill web-archiving -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jamditis/claude-skills-journalism web-archiving --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jamditis/claude-skills-journalism.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/research-toolkit/skills/web-archiving .opencode/skills/web-archiving && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "web-archiving" agent skill from https://github.com/jamditis/claude-skills-journalism/tree/master/research-toolkit/skills/web-archiving into .opencode/skills/web-archiving/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-archiving", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
web-archivingWeb archiving and retrieval via Wayback Machine and Archive.today.
Web Archiving is an agent skill from jamditis/claude-skills-journalism. Web archiving and retrieval via Wayback Machine and Archive.today. Use to preserve content, reach dead pages, or save evidence.
Its SKILL.md is about 6.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `agents/openai.yaml`).
The repository describes itself as: Claude Code skills for journalism, media, and academia - verification, FOIA, data journalism, academic writing, and more. The licence is MIT.
Read from SKILL.md and the folder at commit e3e2172. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
dockercurlFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
web.archive.orgarchive.todaytimetravel.mementoweb.orgarchive.pharchive.orgdocker-compose.archivebox.ioapi.perma.ccperma.ccFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Web Archiving loads about 6.3k tokens when it runs. Until then it costs about 35 tokens; SKILL.md has 439 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from jamditis/claude-skills-journalism at commit e3e2172, republished under its MIT licence (© jamditis). 439 words, ~6,321 tokens.
.claude/skills/web-archiving/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.Patterns for accessing inaccessible web pages and preserving web content for journalism, research, and legal purposes.
<!-- untrusted-content-contract:v1 -->
When this skill retrieves third-party material:
Use this shape when passing retrieved material onward:
<EXTERNAL_DATA source="...">
...
</EXTERNAL_DATA>Try services in this order for maximum coverage:
┌─────────────────────────────────────────────────────────────────┐
│ ARCHIVE RETRIEVAL CASCADE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Wayback Machine (archive.org) │
│ └─ 900B+ pages, historical depth, API access │
│ ↓ not found │
│ 2. Archive.today (archive.is/archive.ph) │
│ └─ On-demand snapshots, paywall bypass │
│ └─ Caveat (2026): FBI subpoenaed registrar in Oct 2025; │
│ Wikipedia deprecated as citation source in Feb 2026, │
│ prefer Wayback / Perma.cc for legal or citation use │
│ ↓ not found │
│ 3. Memento Time Travel (aggregator) │
│ └─ Searches multiple archives simultaneously │
│ │
│ Retired (do not use): Google Cache (`cache:` operator) was │
│ shut down in Sept 2024; Bing Cache dropdown was removed in │
│ the same year. Both formerly fed this cascade. │
│ │
└─────────────────────────────────────────────────────────────────┘import requests
from typing import Optional
from datetime import datetime
from urllib.parse import quote, unquote
def check_wayback_availability(url: str) -> Optional[dict]:
"""Check if URL exists in Wayback Machine."""
api_url = "https://archive.org/wayback/available"
try:
response = requests.get(api_url, params={'url': url}, timeout=10)
data = response.json()
if data.get('archived_snapshots', {}).get('closest'):
snapshot = data['archived_snapshots']['closest']
return {
'available': snapshot.get('available', False),
'url': snapshot.get('url'),
'timestamp': snapshot.get('timestamp'),
'status': snapshot.get('status')
}
return None
except Exception as e:
return None
def get_wayback_url(url: str, timestamp: str = None) -> str:
"""Generate Wayback Machine URL for a page.
Returns the canonical raw form (`.../web/<timestamp>/<url>`) per
Wayback's replay-URL convention. If you intend to navigate to the
returned link in a browser AND the target URL has `#` fragments,
encode at the call site with urllib.parse.quote so the browser
doesn't strip the fragment before request dispatch.
Args:
url: Original URL to retrieve
timestamp: Optional YYYYMMDDHHMMSS format, or None for latest
"""
if timestamp:
return f"https://web.archive.org/web/{timestamp}/{url}"
return f"https://web.archive.org/web/{url}"def save_to_wayback(url: str, s3_keys: Optional[tuple[str, str]] = None) -> Optional[str]:
"""Request Wayback Machine to archive a URL via Save Page Now.
Returns the archived URL if successful.
Anonymous requests are rate-limited at roughly 15/minute. Pass
`s3_keys=(access_key, secret)` from an Internet Archive account
to raise the cap (anonymous → ~50/min with auth) and avoid silent
drops on paywalled / heavily JS-rendered pages.
"""
# quote(unquote(url), ...) normalizes any existing %xx escapes
# first so they don't get double-encoded into %25xx.
save_url = f"https://web.archive.org/save/{quote(unquote(url), safe='')}"
headers = {'User-Agent': 'Mozilla/5.0 (research-archiver)'}
if s3_keys:
headers['Authorization'] = f'LOW {s3_keys[0]}:{s3_keys[1]}'
try:
response = requests.get(save_url, headers=headers, timeout=60)
if response.status_code == 200:
# SPN delivers the canonical archive URL via the final URL
# after redirect-following (or the `Link` header on async
# captures). `response.url` is the reliable common case.
return response.url
return None
except Exception:
return Nonedef get_all_snapshots(url: str, limit: int = 100) -> list[dict]:
"""Get all archived snapshots of a URL using CDX API.
Returns list of snapshots with timestamps and status codes.
"""
cdx_url = "https://web.archive.org/cdx/search/cdx"
params = {
'url': url,
'output': 'json',
'limit': limit,
'fl': 'timestamp,original,statuscode,digest,length'
}
try:
response = requests.get(cdx_url, params=params, timeout=30)
data = response.json()
if len(data) < 2: # First row is headers
return []
headers = data[0]
snapshots = []
for row in data[1:]:
snapshot = dict(zip(headers, row))
snapshot['wayback_url'] = (
f"https://web.archive.org/web/{snapshot['timestamp']}/{snapshot['original']}"
)
snapshots.append(snapshot)
return snapshots
except Exception:
return []import re
import requests
from urllib.parse import quote, unquote, urljoin
def save_to_archive_today(url: str) -> Optional[str]:
"""Submit URL to Archive.today for archiving.
Note: Archive.today has rate limiting and CAPTCHA requirements.
This function works for basic archiving but may require
manual intervention for high-volume use.
Operational notes (2026): the FBI subpoenaed archive.today's
registrar in October 2025; Wikipedia stopped accepting it as a
citation source in February 2026 after the site shipped
DDoS-attack code in January 2026. Still useful for capturing
content the Wayback Machine can't render, but treat as
secondary to Wayback / Perma.cc for legal or citation use.
"""
submit_url = "https://archive.today/submit/"
data = {
'url': url,
'anyway': '1' # Archive even if recent snapshot exists
}
try:
response = requests.post(
submit_url,
data=data,
timeout=60,
allow_redirects=False,
headers={'User-Agent': 'Mozilla/5.0 (research-archiver)'},
)
# archive.today returns the snapshot URL in one of two shapes:
# - 30x with Location: https://archive.today/<snapshot_id>
# (Location MAY be relative per RFC 7231)
# - 200 with Refresh: 0;url=https://archive.today/<snapshot_id>
# (Refresh keyword is case-insensitive per HTML spec)
# Following redirects silently can land on /wip/ pages or hide
# the canonical snapshot URL, so handle both headers explicitly.
if response.status_code in (301, 302, 303, 307, 308):
location = response.headers.get('Location')
if location:
return urljoin(response.url, location)
if response.status_code == 200:
refresh = response.headers.get('Refresh', '')
m = re.search(r'\burl\s*=\s*(.+)', refresh, re.IGNORECASE)
if m:
target = m.group(1).strip().strip('\'"')
return urljoin(response.url, target)
return None
except Exception:
return None
def search_archive_today(url: str) -> Optional[str]:
"""Search for existing Archive.today snapshot.
Uses the /newest/<url> lookup which 302s to the most recent
snapshot (or to a CAPTCHA page if rate-limited).
"""
search_url = f"https://archive.ph/newest/{quote(unquote(url), safe='')}"
try:
response = requests.get(
search_url,
timeout=30,
allow_redirects=False,
headers={'User-Agent': 'Mozilla/5.0 (research-archiver)'},
)
if response.status_code in (301, 302, 303, 307, 308):
location = response.headers.get('Location')
if location:
resolved = urljoin(response.url, location)
# Only return if we landed on an archive page, not CAPTCHA
if 'archive.' in resolved and '/newest/' not in resolved:
return resolved
return None
except Exception:
return Nonefrom dataclasses import dataclass
from typing import Optional, List
from concurrent.futures import ThreadPoolExecutor, as_completed
@dataclass
class ArchiveResult:
service: str
url: str
archived_url: Optional[str]
success: bool
error: Optional[str] = None
class MultiArchiver:
"""Archive URLs to multiple services for redundancy."""
def __init__(self):
self.services = [
('wayback', self._save_wayback),
('archive_today', self._save_archive_today),
('perma_cc', self._save_perma), # Requires API key
]
def archive_url(self, url: str, parallel: bool = True) -> List[ArchiveResult]:
"""Archive URL to all services.
Args:
url: URL to archive
parallel: If True, archive to all services simultaneously
"""
results = []
if parallel:
with ThreadPoolExecutor(max_workers=3) as executor:
futures = {
executor.submit(save_func, url): name
for name, save_func in self.services
}
for future in as_completed(futures):
service = futures[future]
try:
archived_url = future.result()
results.append(ArchiveResult(
service=service,
url=url,
archived_url=archived_url,
success=archived_url is not None
))
except Exception as e:
results.append(ArchiveResult(
service=service,
url=url,
archived_url=None,
success=False,
error=str(e)
))
else:
for name, save_func in self.services:
try:
archived_url = save_func(url)
results.append(ArchiveResult(
service=name,
url=url,
archived_url=archived_url,
success=archived_url is not None
))
except Exception as e:
results.append(ArchiveResult(
service=name,
url=url,
archived_url=None,
success=False,
error=str(e)
))
return results
def _save_wayback(self, url: str) -> Optional[str]:
return save_to_wayback(url)
def _save_archive_today(self, url: str) -> Optional[str]:
return save_to_archive_today(url)
def _save_perma(self, url: str) -> Optional[str]:
# Requires Perma.cc API key
# Implementation depends on having API credentials
return None# Recommended: Docker Compose (v0.8.x ships with Chromium, yt-dlp,
# wget, single-file, and other capture tools preinstalled).
mkdir ~/web-archives && cd ~/web-archives
curl -O 'https://docker-compose.archivebox.io' && mv docker-compose.archivebox.io docker-compose.yml
docker compose run archivebox init --setup
docker compose up -d # start the web UI on :8000
# Pip-based install still works but you'll need to install Chromium /
# yt-dlp / wget / single-file separately for full capture coverage:
# pip install archivebox && archivebox init
# Add URLs to archive (from inside the archive directory)
docker compose run archivebox add "https://example.com/article"
# Add multiple URLs from file
docker compose run archivebox add --depth=0 < urls.txt
# Schedule regular archiving
docker compose run archivebox schedule --every=day --depth=1 "https://example.com/feed.rss"import subprocess
from pathlib import Path
from typing import List, Optional
class ArchiveBoxManager:
"""Manage local ArchiveBox instance."""
def __init__(self, archive_dir: Path):
self.archive_dir = archive_dir
self._ensure_initialized()
def _ensure_initialized(self):
"""Initialize ArchiveBox if needed."""
if not (self.archive_dir / 'index.sqlite3').exists():
subprocess.run(
['archivebox', 'init'],
cwd=self.archive_dir,
check=True
)
def add_url(self, url: str, depth: int = 0) -> bool:
"""Archive a single URL.
Args:
url: URL to archive
depth: 0 for single page, 1 to follow links one level deep
"""
result = subprocess.run(
['archivebox', 'add', f'--depth={depth}', url],
cwd=self.archive_dir,
capture_output=True,
text=True
)
return result.returncode == 0
def add_urls_from_file(self, filepath: Path) -> bool:
"""Archive URLs from a text file (one per line)."""
with open(filepath) as f:
result = subprocess.run(
['archivebox', 'add', '--depth=0'],
cwd=self.archive_dir,
stdin=f,
capture_output=True
)
return result.returncode == 0
def search(self, query: str) -> List[dict]:
"""Search archived content."""
result = subprocess.run(
['archivebox', 'list', '--filter-type=search', query],
cwd=self.archive_dir,
capture_output=True,
text=True
)
# Parse output...
return []import hashlib
import sys
import json
import requests
from datetime import datetime, timezone
from dataclasses import dataclass, asdict
from typing import List
@dataclass
class EvidenceRecord:
"""Legally defensible evidence record."""
# Content identification
original_url: str
archived_urls: List[str] # Multiple archive copies
content_hash_sha256: str
# Timestamps
capture_time_utc: str
first_observed: str
# Metadata
page_title: str
captured_by: str
capture_method: str
tool_versions: dict
# Chain of custody
custody_log: List[dict] # Who accessed when
def add_custody_entry(self, accessor: str, action: str, notes: str = ""):
"""Log access to evidence."""
self.custody_log.append({
'timestamp': datetime.now(timezone.utc).isoformat(),
'accessor': accessor,
'action': action,
'notes': notes
})
def to_json(self) -> str:
return json.dumps(asdict(self), indent=2)
@classmethod
def from_capture(cls, url: str, content: bytes, captured_by: str):
"""Create evidence record from captured content."""
now = datetime.now(timezone.utc).isoformat()
py = sys.version_info
return cls(
original_url=url,
archived_urls=[],
content_hash_sha256=hashlib.sha256(content).hexdigest(),
capture_time_utc=now,
first_observed=now,
page_title="",
captured_by=captured_by,
capture_method="automated_capture",
tool_versions={
# Replace 'archiver' with your tool's actual __version__
'archiver': '1.0.0',
'python': f'{py.major}.{py.minor}.{py.micro}',
'requests': requests.__version__,
},
custody_log=[]
)
def capture_as_evidence(url: str, captured_by: str) -> EvidenceRecord:
"""Capture URL with full evidence chain documentation."""
# Capture content
response = requests.get(url)
content = response.content
# Create evidence record
record = EvidenceRecord.from_capture(url, content, captured_by)
record.page_title = extract_title(content)
# Archive to multiple services
archiver = MultiArchiver()
results = archiver.archive_url(url)
for result in results:
if result.success:
record.archived_urls.append(result.archived_url)
# Log initial capture
record.add_custody_entry(
captured_by,
'initial_capture',
f'Captured from {url}, archived to {len(record.archived_urls)} services'
)
return recordimport requests
from typing import Optional
class PermaCC:
"""Perma.cc API client for legal-grade archiving.
Requires API key from perma.cc (free for limited use).
Used by US courts and legal professionals.
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.perma.cc/v1"
self.headers = {
'Authorization': f'ApiKey {api_key}',
'Content-Type': 'application/json'
}
def create_archive(self, url: str, folder_id: int = None) -> Optional[dict]:
"""Create a new Perma.cc archive.
Returns dict with guid, creation_timestamp, and captures.
"""
data = {'url': url}
if folder_id:
data['folder'] = folder_id
try:
response = requests.post(
f"{self.base_url}/archives/",
json=data,
headers=self.headers,
timeout=60
)
if response.status_code == 201:
result = response.json()
return {
'guid': result['guid'],
'url': f"https://perma.cc/{result['guid']}",
'creation_timestamp': result['creation_timestamp'],
'title': result.get('title', '')
}
return None
except Exception:
return None
def get_archive(self, guid: str) -> Optional[dict]:
"""Retrieve archive metadata by GUID."""
try:
response = requests.get(
f"{self.base_url}/archives/{guid}/",
headers=self.headers,
timeout=30
)
return response.json() if response.status_code == 200 else None
except Exception:
return None// Save to Wayback Machine - add as bookmark
javascript:(function(){
window.open('https://web.archive.org/save/' + encodeURIComponent(location.href), '_blank');
})();
// Save to Archive.today
javascript:(function(){
window.open('https://archive.today/?run=1&url=' + encodeURIComponent(location.href), '_blank');
})();
// Check all archives (Memento)
javascript:(function(){
window.open('https://timetravel.mementoweb.org/list/0/' + encodeURIComponent(location.href), '_blank');
})();// Try multiple archives for dead pages
// Note: Google Cache (webcache.googleusercontent.com) was retired in
// Sept 2024 and is omitted here.
javascript:(function(){
// Encode location.href so any '#' / '?' inside it travels as part
// of the path argument; raw concatenation lets the browser strip
// fragments and re-attach query strings to the outer URL.
var encoded = encodeURIComponent(location.href);
var archives = [
'https://web.archive.org/web/*/' + encoded,
'https://archive.ph/newest/' + encoded,
'https://timetravel.mementoweb.org/list/0/' + encoded
];
archives.forEach(function(a){ window.open(a, '_blank'); });
})();| Service | Best For | API | Deletions | Max Size | Notes |
|---|---|---|---|---|---|
| Wayback Machine | Historical research | Yes (free) | On request | Unlimited | Anonymous SPN ~15/min; auth via S3 keys raises cap |
| Archive.today | Paywall bypass, quick saves | Informal | Never | 50MB | FBI subpoena Oct 2025; Wikipedia deprecated as citation source Feb 2026, avoid for legal/citation use |
| Perma.cc | Legal citations | Yes (free tier) | By creator | Standard pages | Used by US courts; Authorization: ApiKey <key> |
| ArchiveBox | Self-hosted, privacy | Local | Never | Disk space | v0.8 ships Docker Compose with Chromium / yt-dlp / wget |
| Browsertrix Cloud | Interactive / JS-heavy capture | Yes | By creator | Plan-based | Webrecorder.net successor to Conifer; outputs WARC |
| Conifer | Interactive content | Yes | By creator | 5GB free | Older Webrecorder service; Browsertrix Cloud is the active path |
import requests
from enum import Enum
from typing import Optional
from urllib.parse import quote, unquote
class ArchiveError(Enum):
NOT_FOUND = "No archive found"
RATE_LIMITED = "Rate limited by service"
BLOCKED = "URL blocked from archiving"
TIMEOUT = "Request timed out"
SERVICE_DOWN = "Archive service unavailable"
def get_archived_page(url: str) -> tuple[Optional[str], Optional[ArchiveError]]:
"""Try all archive services with proper error handling."""
# 1. Try Wayback Machine first
try:
result = check_wayback_availability(url)
if result and result.get('available'):
return result['url'], None
except requests.Timeout:
pass # Try next service
except Exception:
pass
# 2. Try Archive.today
try:
result = search_archive_today(url)
if result:
return result, None
except Exception:
pass
# 3. Try Memento aggregator
try:
memento_url = f"https://timetravel.mementoweb.org/api/json/0/{quote(unquote(url), safe='')}"
response = requests.get(memento_url, timeout=30)
data = response.json()
if data.get('mementos', {}).get('closest'):
return data['mementos']['closest']['uri'][0], None
except Exception:
pass
return None, ArchiveError.NOT_FOUNDAlways archive to at least two services:
def ensure_archived(url: str) -> bool:
"""Ensure URL is archived in at least 2 services."""
archiver = MultiArchiver()
results = archiver.archive_url(url)
successful = [r for r in results if r.success]
return len(successful) >= 2robots.txt for bulk archiving© jamditis, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in research-toolkit/skills/web-archiving of jamditis/claude-skills-journalism.
Open the folder on GitHubat commit e3e2172
Web Archiving next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Web Archiving this skilljamditis/claude-skills-journalism | 416 | — | ~6.3k | Automated safety check: Pass | MIT | |
| Iterative Retrievalaffaan-m/ECC | 275k | 7 repos | ~1.6k | Automated safety check: Pass | MIT | |
| Iterative Retrievalaffaan-m/ECC | 275k | 2 repos | ~1.1k | Automated safety check: Pass | MIT | |
| Iterative Retrievalaffaan-m/ECC | 275k | 2 repos | ~1.3k | Automated safety check: Pass | MIT | |
| Iterative Retrievalaffaan-m/ECC | 275k | 1 repos | ~1.1k | Automated safety check: Pass | MIT | |
| OpenSpec Bulk Change ArchiverFission-AI/OpenSpec | 71k | 3 repos | ~5.6k | Automated safety check: Pass | MIT |
affaan-m/ECC
Pattern for progressively refining context retrieval to solve the subagent context problem.
affaan-m/ECC
サブエージェントのコンテキスト問題を解決するために、コンテキスト取得を段階的に洗練するパターン. An agent skill from affaan-m/ECC.
affaan-m/ECC
서브에이전트 컨텍스트 문제를 해결하기 위한 점진적 컨텍스트 검색 개선 패턴. An agent skill from affaan-m/ECC.
affaan-m/ECC
逐步优化上下文检索以解决子代理上下文问题的模式
Fission-AI/OpenSpec
Archives several completed OpenSpec changes in one operation, checking the codebase to resolve spec conflicts rather than archiving blindly.
divinevideo/divine-mobile
Recover data from archived REST APIs using Wayback Machine. An agent skill from divinevideo/divine-mobile.
jamditis/claude-skills-journalism
A skill your agent uses when creating distinct website directions, a client review picker, asset catalog, previews, and Cloudflare-ready handoffs.
jamditis/claude-skills-journalism
Builds an Open Knowledge Format (OKF) knowledge base from existing docs, notes, or a repo.
jamditis/claude-skills-journalism
Local Gitleaks scans for staged changes, push ranges, and full history in private repos, with redacted reports.
jamditis/claude-skills-journalism
Acquire, clean, analyze, verify, visualize, and explain data for journalism.
jamditis/claude-skills-journalism
Creates print-ready HTML that exports to PDF. An agent skill from jamditis/claude-skills-journalism.
jamditis/claude-skills-journalism
Establishes how to find and use skills, requiring Skill tool invocation before any response.
Web archiving and retrieval via Wayback Machine and Archive.today. Web Archiving is an agent skill from jamditis/claude-skills-journalism.today.
Web Archiving fits situations like: preserve content; reach dead pages.
Run `npx skills add jamditis/claude-skills-journalism --skill web-archiving -a claude-code`. Or copy the skill folder (research-toolkit/skills/web-archiving in jamditis/claude-skills-journalism) into .claude/skills/web-archiving in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jamditis/claude-skills-journalism --skill web-archiving -a codex`. Or copy the skill folder (research-toolkit/skills/web-archiving in jamditis/claude-skills-journalism) into .agents/skills/web-archiving in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jamditis/claude-skills-journalism --skill web-archiving -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/web-archiving, .gemini/skills/web-archiving, .github/skills/web-archiving and .opencode/skills/web-archiving in your project.
Going by SKILL.md and its folder, Web Archiving needs the command-line tools its instructions call (docker and curl). Our summary lists: Python 3.
SKILL.md names 8 domains. In commands or code: web.archive.org, archive.today, timetravel.mementoweb.org, archive.ph, archive.org, docker-compose.archivebox.io, api.perma.cc and perma.cc; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Web Archiving is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.3k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Web Archiving: Iterative Retrieval (affaan-m/ECC, 275k stars), Iterative Retrieval (affaan-m/ECC, 275k stars), Iterative Retrieval (affaan-m/ECC, 275k stars) and Iterative Retrieval (affaan-m/ECC, 275k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jamditis (a GitHub user) maintains it in jamditis/claude-skills-journalism, which has 416 GitHub stars. The repository holds 53 skills in this directory. The repository was last updated on October 4, 2026.
Source: jamditis/claude-skills-journalism on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.