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
45 changes: 10 additions & 35 deletions build_by_month.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,44 +31,19 @@ def _get_first_n_words(text: str, n: int = 15) -> str:
return " ".join(words[:n]) + "..."


def _extract_summary(docs_path: Path, word_limit: int = 30) -> str:
"""Extract the first paragraph of the docs file, limited to word_limit words."""
if not docs_path.exists():
def _extract_summary(meta_path: Path, word_limit: int = 30) -> str:
"""Extract the description from the meta JSON file, limited to word_limit words."""
if not meta_path.exists():
return ""

try:
content = docs_path.read_text("utf-8").strip()
except OSError:
import json
data = json.load(meta_path.open("r", encoding="utf-8"))
description = data.get("description", "")
return _get_first_n_words(description, word_limit)
except (OSError, json.JSONDecodeError):
return ""

# Remove HTML comments
if "<!--" in content:
content = content.split("<!--", 1)[0]

# Strip any markdown heading lines first
content_lines = [
line for line in content.splitlines()
if not line.lstrip().startswith("# ")
and not line.lstrip().startswith("## ")
and not line.lstrip().startswith("### ")
and not line.lstrip().startswith("#### ")
and not line.lstrip().startswith("##### ")
and not line.lstrip().startswith("###### ")
]

# Get first paragraph
lines = []
for line in content_lines:
stripped = line.strip()
if not stripped:
if lines:
break
continue
lines.append(stripped)

paragraph = " ".join(lines)
return _get_first_n_words(paragraph, word_limit)


def _load_gathered_links() -> dict:
if not GATHERED_LINKS_PATH.exists():
Expand Down Expand Up @@ -105,8 +80,8 @@ def build_by_month() -> None:

# Get the docs summary
slug = page_name.replace(".html", "")
docs_path = Path(f"{slug}.docs.md")
summary = _extract_summary(docs_path)
meta_path = Path("meta") / f"{slug}.json"
summary = _extract_summary(meta_path)

tools_by_month[month_key].append({
"filename": page_name,
Expand Down
34 changes: 13 additions & 21 deletions build_colophon.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,29 +260,21 @@ def get_most_recent_date(page_data):
</h2>
</div>
"""
# Check for corresponding docs.md file
docs_file = page_name.replace(".html", ".docs.md")
if Path(docs_file).exists():
# Check for corresponding meta JSON file
slug = page_name.replace(".html", "")
meta_file = Path("meta") / f"{slug}.json"
if meta_file.exists():
try:
with open(docs_file, "r") as f:
docs_content = f.read()
# Strip any markdown heading lines first
docs_lines = [
line for line in docs_content.splitlines()
if not line.lstrip().startswith("# ")
and not line.lstrip().startswith("## ")
and not line.lstrip().startswith("### ")
and not line.lstrip().startswith("#### ")
and not line.lstrip().startswith("##### ")
and not line.lstrip().startswith("###### ")
]
docs_content = "\n".join(docs_lines)
# Render markdown to HTML
docs_html = markdown.markdown(docs_content)
# Add docs above commits
html_content += '<div class="docs">' + docs_html + "</div>"
with open(meta_file, "r") as f:
meta_data = json.load(f)
description = meta_data.get("description", "")
if description:
# Render markdown to HTML
docs_html = markdown.markdown(description)
# Add docs above commits
html_content += '<div class="docs">' + docs_html + "</div>"
except Exception as e:
print(f"Error reading {docs_file}: {e}")
print(f"Error reading {meta_file}: {e}")

# Wrap commits in details/summary tags
html_content += f"""
Expand Down
84 changes: 84 additions & 0 deletions docs_to_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Convert *.docs.md files to JSON files in meta/ directory."""

import json
import re
from pathlib import Path


def extract_description(content: str) -> str:
"""Extract the first paragraph (description) from docs.md content."""
# Remove HTML comments
if "<!--" in content:
content = content.split("<!--", 1)[0]

# Strip any markdown heading lines
content_lines = [
line for line in content.splitlines()
if not line.lstrip().startswith("# ")
and not line.lstrip().startswith("## ")
and not line.lstrip().startswith("### ")
and not line.lstrip().startswith("#### ")
and not line.lstrip().startswith("##### ")
and not line.lstrip().startswith("###### ")
]

# Get first paragraph
lines = []
for line in content_lines:
stripped = line.strip()
if not stripped:
if lines:
break
continue
lines.append(stripped)

return " ".join(lines)


def extract_commit(content: str) -> str:
"""Extract the commit hash from the HTML comment."""
match = re.search(r"<!-- Generated from commit: ([a-f0-9]+) -->", content)
if match:
return match.group(1)
return ""


def main():
# Create meta directory if it doesn't exist
meta_dir = Path("meta")
meta_dir.mkdir(exist_ok=True)

# Find all docs.md files in the current directory
docs_files = sorted(Path(".").glob("*.docs.md"))

converted_count = 0

for docs_file in docs_files:
# Read the content
content = docs_file.read_text("utf-8")

# Extract description and commit
description = extract_description(content)
commit = extract_commit(content)

# Determine output filename (e.g., ai-adoption.docs.md -> meta/ai-adoption.json)
slug = docs_file.stem.replace(".docs", "")
output_file = meta_dir / f"{slug}.json"

# Create JSON object
data = {
"description": description,
"commit": commit,
}

# Write to file with pretty printing
output_file.write_text(json.dumps(data, indent=2) + "\n", "utf-8")
converted_count += 1
print(f"Converted {docs_file} -> {output_file}")

print(f"\nConverted {converted_count} files to JSON in meta/")


if __name__ == "__main__":
main()
29 changes: 8 additions & 21 deletions gather_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,17 @@ def extract_urls(text):
return re.findall(url_pattern, text)


def extract_description(docs_path: Path) -> str:
"""Extract the first paragraph of the generated docs markdown file."""
if not docs_path.exists():
def extract_description(meta_path: Path) -> str:
"""Extract the description from the meta JSON file."""
if not meta_path.exists():
return ""

try:
content = docs_path.read_text("utf-8").strip()
except OSError:
data = json.load(meta_path.open("r", encoding="utf-8"))
return data.get("description", "")
except (OSError, json.JSONDecodeError):
return ""

if "<!--" in content:
content = content.split("<!--", 1)[0]

lines = []
for line in content.splitlines():
stripped = line.strip()
if not stripped:
if lines:
break
continue
lines.append(stripped)

return " ".join(lines)


def extract_title(html_path: Path) -> str:
"""Extract the <title> from an HTML file."""
Expand Down Expand Up @@ -147,8 +134,8 @@ def main():
if not commits:
continue

docs_path = html_file.with_suffix(".docs.md")
description = extract_description(docs_path)
meta_path = Path("meta") / f"{html_file.stem}.json"
description = extract_description(meta_path)

created_date = commits[-1]["date"] if commits else None
updated_date = commits[0]["date"] if commits else None
Expand Down
4 changes: 4 additions & 0 deletions meta/ai-adoption.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "View AI adoption trends across different firm sizes by analyzing survey data on artificial intelligence usage in the workplace. This page runs a Python analysis using Pyodide to fetch employment survey data, calculate six-survey rolling averages, and generate an interactive visualization showing adoption rates by company size from November 2023 through August 2025. Download the resulting chart as PNG or SVG for further use or presentation.",
"commit": "ecc4d0ed023901a9d26d99aea2b3bd34258e5241"
}
4 changes: 4 additions & 0 deletions meta/alt-text-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Extract alternative text and image URLs from rich text content pasted into this tool. Simply paste content from web pages, and the tool automatically detects embedded images, displays them alongside their alt text descriptions, and provides copy buttons for convenient access to both the alt text and image URLs. This utility is particularly useful for accessibility audits, content analysis, and archiving image metadata.",
"commit": "e468d4b9566e27850d234e609f9d99cafbd9c78b"
}
4 changes: 4 additions & 0 deletions meta/analytics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Track your browsing activity and viewing patterns with this personal analytics dashboard that stores all data locally in your browser. The tool displays comprehensive statistics including total visits, unique pages visited, and time-based breakdowns through interactive charts showing visits by day or hour. A detailed table view shows your most visited pages and recent activity, with options to export your analytics data as JSON or clear all stored information at any time.",
"commit": "41099d1fcf99cf91a2d2be88e96597abc776ef21"
}
4 changes: 4 additions & 0 deletions meta/animated-rainbow-border.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Display an animated rainbow gradient border effect around a centered box with interactive controls. The page features a dark theme with a glowing, color-shifting border that can be toggled on and off using the provided button. The animation combines gradient shifting and pulsing effects to create a dynamic, eye-catching visual presentation.",
"commit": "99021c5a96c4d188e92a9341621a105e0d3600ca"
}
4 changes: 4 additions & 0 deletions meta/annotated-presentations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Create annotated presentation slides with alt text and markdown notes. Upload your slide images, add accessibility descriptions and annotations with markdown support, then generate HTML output using customizable templates. The tool automatically saves your work and includes optional OCR functionality to extract text from slides.",
"commit": "6c429482d2b7eb09c5e2554fe33c6157137f7bcc"
}
4 changes: 4 additions & 0 deletions meta/apsw-query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Analyze and explain SQLite queries using APSW by entering SQL code and executing it in an in-browser Python environment. The tool provides detailed query analysis including execution plans, expanded SQL, and query information to help understand how SQLite processes your queries. Optional setup SQL can be run before the main query to create tables or initialize data, and parameterized queries are supported through labeled input fields.",
"commit": "0af31729167e3de7f6ac73afd5e5bc03ba3b68fb"
}
4 changes: 4 additions & 0 deletions meta/arena-animated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Compare Elo ratings across different language models and dates using this interactive animated bar chart. Load your own JSON data with model names, dates, and Elo scores, then watch the rankings evolve over time with customizable animation speed. The visualization displays the top 20 models at each time point, making it easy to track performance trends and competitive dynamics in the AI landscape.",
"commit": "9a2450df1bf8f426e243dc5f4964f0adc725de76"
}
4 changes: 4 additions & 0 deletions meta/ares.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Convert text to NATO phonetic alphabet equivalents for clear communication in radio, military, and aviation contexts. Enter any combination of letters and numbers, then click the convert button to display the phonetic representation with each character separated by spaces. Spaces in the original text are marked as \"(SPACE)\" in the output.",
"commit": "9da6f96d4e3c886f1776cdc5b658e47087a279dd"
}
4 changes: 4 additions & 0 deletions meta/aria-live-regions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Explore live region notifications with this interactive accessibility demo that allows you to test how screen readers announce dynamic content updates. The page provides instructions for testing with VoiceOver on macOS and iOS, plus interactive controls to trigger notifications with different aria-live settings (assertive or polite) to observe how screen readers prioritize and announce content changes.",
"commit": "30ad8d445379deeab726dc7d6c94a67e18de2f32"
}
4 changes: 4 additions & 0 deletions meta/audio-spectrum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Visualize real-time audio frequency data from your microphone as an animated spectrum display. This application uses the Web Audio API to capture microphone input, analyze frequencies using FFT (Fast Fourier Transform), and render dynamic bars that respond to sound levels across the frequency spectrum. Grant microphone permissions when prompted to begin the visualization.",
"commit": "1390c401fa2e7a1797dba733a059390752a8a62a"
}
4 changes: 4 additions & 0 deletions meta/avatar-web-component.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Display and edit images with an interactive cropping interface for creating avatars or profile pictures. Users can select images by clicking, dragging and dropping files, or pasting from the clipboard, then crop them to a specified aspect ratio using resizable handles. The final cropped image is automatically exported as a JPEG data URL and can be synced to a designated form input field for seamless integration with web forms.",
"commit": "51b013005bea273913d5501fb6355c9aff93a97d"
}
4 changes: 4 additions & 0 deletions meta/badge-drawer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Draw pixel art for e-ink badge displays with this web-based canvas editor supporting multiple brush sizes, undo/redo functionality, and real-time black-and-white preview. Export your designs as PNG files or transfer them directly to Badger2040 devices running MicroPython via Web Serial. The editor maintains device-native resolution while offering adjustable zoom levels and includes features like grid overlay, background image loading, and URL-based bookmarking for saving your work.",
"commit": "914fd7c3e36f51e88c6674ffa2d9557f881c8072"
}
4 changes: 4 additions & 0 deletions meta/badge-repl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Interact with a MicroPython device via the Web Serial API to execute Python commands in real-time through a browser-based REPL interface. This tool enables direct communication with compatible microcontroller boards, allowing users to run Python code, query system information, and manage files without requiring terminal software or drivers. The interface provides quick-access buttons for common operations like listing files, checking CPU frequency, and monitoring available memory.",
"commit": "5cdacf6eb6ce834dd3814386e6860ec22af5fd5d"
}
4 changes: 4 additions & 0 deletions meta/base64-gzip-decoder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Decode base64-encoded gzip data to retrieve the original decompressed content. Paste your base64 string into the input field and click the Decode button to process the data. The decoder handles the conversion from base64 format and gzip decompression in sequence, displaying the result or providing detailed error messages if the input is invalid.",
"commit": "2ef8cb4d03cd338b88ee72f8d811d839f311c525"
}
4 changes: 4 additions & 0 deletions meta/bbox-cropper.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Draw bounding boxes on images using an interactive cropping tool powered by CropperJS. Load an image by pasting, dragging and dropping, or selecting a file, then click and drag to create a box around your region of interest. The tool automatically outputs normalized coordinates as percentages of the image dimensions in a format ready for command-line use.",
"commit": "d117432950c0c71f304ef9c89da6c91a83883df4"
}
4 changes: 4 additions & 0 deletions meta/blackened-cauliflower-and-turkish-style-stew.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Track and manage two simultaneous recipes with this interactive cooking timer that displays the current step, upcoming tasks, and a complete timeline with clock times. The timer persists across browser sessions, allowing you to pause and resume your cooking progress at any point. Color-coded tags distinguish between the Cauliflower & Couscous and Chickpea Stew recipes to help you stay organized while juggling multiple dishes.",
"commit": "d26b2be2f78c1c2539b38bb9e5d8b1a9c6831a6c"
}
4 changes: 4 additions & 0 deletions meta/blog-to-newsletter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Create a newsletter from Simon Willison's blog posts, links, and other content by selecting content from the past several days and arranging it in your preferred order. The tool fetches data from a Datasette backup and integrates with Substack by comparing against previous newsletter RSS feeds to avoid duplicating content. Generate and copy the formatted HTML directly to your clipboard for pasting into Substack, with options to include or exclude various content types and preview the final newsletter layout.",
"commit": "db7b5027dc970d4479426f5b674759c8bcbc8629"
}
4 changes: 4 additions & 0 deletions meta/bluesky-faves.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "View liked posts from any Bluesky user by entering their profile URL, handle, or DID. This application fetches and displays a user's favorite posts with full post details including text, images, and engagement metrics, allowing you to browse and export the content in multiple formats. Simply enter a Bluesky profile identifier and the viewer will retrieve up to 200 of their most recent likes with the ability to load additional posts.",
"commit": "1eef44de4d0fd263637cda4aef562e49c976ad30"
}
4 changes: 4 additions & 0 deletions meta/bluesky-firehose.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Monitor real-time Bluesky feed data by connecting to the Bluesky Jetstream WebSocket service and viewing incoming posts and events. Send custom JSON messages to filter the feed by collection type, specific user DIDs, or other parameters, with all activity logged in the output panel for debugging and inspection. Use the provided keyboard shortcut (Ctrl/Cmd + Enter) to quickly send configuration updates to the WebSocket connection.",
"commit": "7c6af8eeabc7682b5f9ec2621e34bc771c5471d8"
}
4 changes: 4 additions & 0 deletions meta/bluesky-quote-finder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Search for quote posts on Bluesky by entering a post URL to discover all responses that quote the original post. The tool fetches and displays these quotes with options to sort by likes, recency, or oldest first, and provides direct links to view each quote on Bluesky or explore its thread context. A color-coded depth indicator helps visualize the conversation hierarchy.",
"commit": "d61f13e06cb664aee69fbe12c21a472f2c1a0daf"
}
4 changes: 4 additions & 0 deletions meta/bluesky-resolve.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "View and resolve Bluesky handles to their corresponding Decentralized Identifiers (DIDs) using the AT Protocol API. Enter a Bluesky handle in the format `username.bsky.social` to retrieve the associated DID, which serves as a unique identifier for accounts on the Bluesky network. This tool provides a simple interface for looking up handle-to-DID mappings through Bluesky's public resolver endpoint.",
"commit": "1472c5df0a4a8e97ee67543600403e642d504f0b"
}
4 changes: 4 additions & 0 deletions meta/bluesky-search.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Search Bluesky posts using advanced filters and options to organize results by latest or top engagement. This tool requires authentication with your Bluesky account credentials and supports filtering by date range, author, mentions, language, domain, URL, and hashtags. Results can be exported as formatted markdown for easy sharing and documentation.",
"commit": "86aaf4865dc096a40771889e2b6a8abf3fe97c0d"
}
4 changes: 4 additions & 0 deletions meta/bluesky-thread.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "View Bluesky thread conversations with nested reply hierarchies and chronological sorting options. This viewer fetches posts from public Bluesky APIs and displays them with color-coded depth levels, embedded media support, and interactive features like quoted post previews and image galleries. Users can toggle between thread view (showing reply nesting) and chronological view (most recent first), with the ability to hide non-author replies in thread mode for easier navigation through long conversations.",
"commit": "2854cfeb292a24478bb9db568d67bb8365c66e96"
}
Loading
Loading