├── .github └── workflows │ ├── pages.yml │ └── test.yml ├── CNAME ├── LICENSE ├── README.md ├── animated-rainbow-border.docs.md ├── animated-rainbow-border.html ├── annotated-presentations.docs.md ├── annotated-presentations.html ├── apsw-query.docs.md ├── apsw-query.html ├── arena-animated.docs.md ├── arena-animated.html ├── ares.docs.md ├── ares.html ├── aria-live-regions.docs.md ├── aria-live-regions.html ├── audio-spectrum.docs.md ├── audio-spectrum.html ├── avatar-web-component.docs.md ├── avatar-web-component.html ├── base64-gzip-decoder.docs.md ├── base64-gzip-decoder.html ├── bash ├── README.md ├── extract-file-history.sh └── mem.sh ├── bbox-cropper.docs.md ├── bbox-cropper.html ├── bluesky-firehose.docs.md ├── bluesky-firehose.html ├── bluesky-resolve.docs.md ├── bluesky-resolve.html ├── bluesky-thread.docs.md ├── bluesky-thread.html ├── bluesky-timeline.docs.md ├── bluesky-timeline.html ├── box-shadow.docs.md ├── box-shadow.html ├── broadcast-channel-chat.docs.md ├── broadcast-channel-chat.html ├── build_colophon.py ├── california-clock-change.docs.md ├── california-clock-change.html ├── census-reporter-claude.docs.md ├── census-reporter-claude.html ├── census-reporter-gemini.docs.md ├── census-reporter-gemini.html ├── chrome-prompt-playground.docs.md ├── chrome-prompt-playground.html ├── claude-token-counter.docs.md ├── claude-token-counter.html ├── click-grid-to-expand.docs.md ├── click-grid-to-expand.html ├── clipboard-viewer.docs.md ├── clipboard-viewer.html ├── cloudflare-workers └── github-auth.js ├── code-with-claude-2025.docs.md ├── code-with-claude-2025.html ├── compare-pdfs.docs.md ├── compare-pdfs.html ├── css-text-wrapping.docs.md ├── css-text-wrapping.html ├── csv-marker-map.docs.md ├── csv-marker-map.html ├── date-calculator.docs.md ├── date-calculator.html ├── dev-requirements.txt ├── encrypt.docs.md ├── encrypt.html ├── escape-entities.docs.md ├── escape-entities.html ├── event-planner.docs.md ├── event-planner.html ├── exif.docs.md ├── exif.html ├── extract-urls.docs.md ├── extract-urls.html ├── favicon.ico ├── flexbox-playground.docs.md ├── flexbox-playground.html ├── footnotes-experiment.docs.md ├── footnotes-experiment.html ├── gather_links.py ├── gemini-bbox-tool.docs.md ├── gemini-bbox-tool.html ├── gemini-bbox.docs.md ├── gemini-bbox.html ├── gemini-chat.docs.md ├── gemini-chat.html ├── gemini-image-json.docs.md ├── gemini-image-json.html ├── gemini-mask.docs.md ├── gemini-mask.html ├── github-api-write.docs.md ├── github-api-write.html ├── github-issue-to-markdown.docs.md ├── github-issue-to-markdown.html ├── github-issue.docs.md ├── github-issue.html ├── gpt-4o-audio-player.docs.md ├── gpt-4o-audio-player.html ├── hacker-news-thread-export.docs.md ├── hacker-news-thread-export.html ├── haiku.docs.md ├── haiku.html ├── html-preview.docs.md ├── html-preview.html ├── huggingface-storage.docs.md ├── huggingface-storage.html ├── iframe-api-explorer.docs.md ├── iframe-api-explorer.html ├── iframe-sandbox.docs.md ├── iframe-sandbox.html ├── image-resize-quality.docs.md ├── image-resize-quality.html ├── image-to-jpeg.docs.md ├── image-to-jpeg.html ├── image-to-svg.docs.md ├── image-to-svg.html ├── incomplete-json-printer.docs.md ├── incomplete-json-printer.html ├── jina-embeddings-image-token-calculator.docs.md ├── jina-embeddings-image-token-calculator.html ├── jina-reader.docs.md ├── jina-reader.html ├── json-schema-builder.docs.md ├── json-schema-builder.html ├── json-to-yaml.docs.md ├── json-to-yaml.html ├── keyboard-filters.docs.md ├── keyboard-filters.html ├── lightning-timer.docs.md ├── lightning-timer.html ├── link-temp.docs.md ├── link-temp.html ├── llm-prices.docs.md ├── llm-prices.html ├── markdown-math.docs.md ├── markdown-math.html ├── mask-visualizer.docs.md ├── mask-visualizer.html ├── mdn-timelines.docs.md ├── mdn-timelines.html ├── nav-for-headings.docs.md ├── nav-for-headings.html ├── ocr.docs.md ├── ocr.html ├── openai-audio-output.docs.md ├── openai-audio-output.html ├── openai-audio.docs.md ├── openai-audio.html ├── openai-webrtc.docs.md ├── openai-webrtc.html ├── openfreemap-demo.docs.md ├── openfreemap-demo.html ├── passkeys.docs.md ├── passkeys.html ├── paste-html-subset.docs.md ├── paste-html-subset.html ├── paste-rich-text.docs.md ├── paste-rich-text.html ├── pdf-ocr.docs.md ├── pdf-ocr.html ├── php-deserializer.docs.md ├── php-deserializer.html ├── pipfile.docs.md ├── pipfile.html ├── pomodoro.docs.md ├── pomodoro.html ├── progress.docs.md ├── progress.html ├── prompts-js.docs.md ├── prompts-js.html ├── python ├── README.md ├── all_gcp_buckets.py ├── debug_s3_access.py ├── extract_har.py ├── extract_sourcemap.py ├── gguf_inspect.py ├── highlight.py ├── http_check.py ├── json_extractor.py ├── magic_bucket.py ├── mistral_ocr.py └── whitespace_cleaner.py ├── qr.docs.md ├── qr.html ├── render-claude-citations.docs.md ├── render-claude-citations.html ├── render-markdown.docs.md ├── render-markdown.html ├── rtf-to-html.docs.md ├── rtf-to-html.html ├── schema-dsl.docs.md ├── schema-dsl.html ├── side-panel-dialog.docs.md ├── side-panel-dialog.html ├── social-media-cropper.docs.md ├── social-media-cropper.html ├── species-observation-map.docs.md ├── species-observation-map.html ├── sql-pretty-printer.docs.md ├── sql-pretty-printer.html ├── sqlite-wasm.docs.md ├── sqlite-wasm.html ├── svg-progressive-render.docs.md ├── svg-progressive-render.html ├── svg-render.docs.md ├── svg-render.html ├── svg-sandbox.docs.md ├── svg-sandbox.html ├── swagger-subset.docs.md ├── swagger-subset.html ├── tests ├── ocr-test-text.png ├── requirements.txt ├── test_ocr.py └── three_page_pdf.pdf ├── text-wrap-balance-nav.docs.md ├── text-wrap-balance-nav.html ├── tiff-orientation.docs.md ├── tiff-orientation.html ├── timezones.docs.md ├── timezones.html ├── token-usage.docs.md ├── token-usage.html ├── transfer-time.docs.md ├── transfer-time.html ├── unix-timestamp.docs.md ├── unix-timestamp.html ├── user-agent.docs.md ├── user-agent.html ├── vercel └── anthropic-proxy │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── vercel.json ├── word-counter.docs.md ├── word-counter.html ├── write_docs.py ├── writing-style.docs.md ├── writing-style.html ├── yaml-explorer.docs.md ├── yaml-explorer.html ├── youtube-thumbnails.docs.md ├── youtube-thumbnails.html ├── zip-wheel-explorer.docs.md └── zip-wheel-explorer.html /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | # Run on push to main branch 5 | push: 6 | branches: [ main ] 7 | # Allow manual trigger 8 | workflow_dispatch: 9 | 10 | # Sets permissions of the GITHUB_TOKEN 11 | permissions: 12 | contents: write 13 | pages: write 14 | id-token: write 15 | 16 | # Allow only one concurrent deployment 17 | concurrency: 18 | group: pages 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 # Need full history for git log in gather_links.py 29 | 30 | - name: Setup Python 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: '3.12' 34 | 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install markdown llm llm-anthropic 38 | 39 | - name: Generate links and build colophon 40 | env: 41 | ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} 42 | run: | 43 | python gather_links.py 44 | python write_docs.py 45 | python build_colophon.py 46 | 47 | - name: Find modified docs stems 48 | id: docs 49 | run: | 50 | # Ensure newly created files are visible to git diff 51 | git add '*.docs.md' 52 | # Identify modified files 53 | MODIFIED_FILES=$(git diff --name-only HEAD -- '*.docs.md') 54 | if [ -z "$MODIFIED_FILES" ]; then 55 | echo "stems=" >> $GITHUB_OUTPUT 56 | else 57 | STEMS=$(echo "$MODIFIED_FILES" \ 58 | | sed 's/\.docs\.md$//' \ 59 | | paste -sd ', ' -) 60 | echo "stems=$STEMS" >> $GITHUB_OUTPUT 61 | fi 62 | 63 | - name: Commit and push if docs have changed 64 | if: steps.docs.outputs.stems != '' 65 | run: |- 66 | git config user.name "Automated" 67 | git config user.email "actions@users.noreply.github.com" 68 | git add *.docs.md 69 | git commit -m "Generated docs: ${{ steps.docs.outputs.stems }}" || exit 0 70 | git push 71 | 72 | - name: Setup Pages 73 | uses: actions/configure-pages@v4 74 | 75 | - name: Build with Jekyll 76 | uses: actions/jekyll-build-pages@v1 77 | 78 | - name: Upload artifact 79 | uses: actions/upload-pages-artifact@v3 80 | 81 | deploy: 82 | environment: 83 | name: github-pages 84 | url: ${{ steps.deployment.outputs.page_url }} 85 | runs-on: ubuntu-latest 86 | needs: build 87 | steps: 88 | - name: Deploy to GitHub Pages 89 | id: deployment 90 | uses: actions/deploy-pages@v4 91 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up Python 3.12 16 | uses: actions/setup-python@v5 17 | with: 18 | python-version: "3.12" 19 | cache: 'pip' 20 | cache-dependency-path: 'dev-requirements.txt' 21 | - name: Cache Playwright browsers 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.cache/ms-playwright/ 25 | key: ${{ runner.os }}-browsers 26 | - name: Install dependencies 27 | run: | 28 | pip install -r dev-requirements.txt 29 | playwright install 30 | - name: Run test 31 | run: | 32 | pytest 33 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | tools.simonwillison.net -------------------------------------------------------------------------------- /animated-rainbow-border.docs.md: -------------------------------------------------------------------------------- 1 | This page displays a container with a "Rainbow Border" label and a toggle button to control animation effects. When activated, the container shows an animated rainbow-colored glowing border that pulses and changes color gradients in a continuous flow. The animation uses CSS keyframes to create a smooth transition between colors and opacity levels, giving the border a vibrant, dynamic appearance that loops infinitely until deactivated. 2 | 3 | -------------------------------------------------------------------------------- /animated-rainbow-border.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 128 | 129 | 130 |
131 |
Rainbow Border
132 |
133 | 134 | 135 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /annotated-presentations.docs.md: -------------------------------------------------------------------------------- 1 | The Annotated Presentation Creator allows users to upload presentation slides, add accessibility alt text, and create markdown annotations for each slide. Users can upload multiple image files, write descriptive alt text, and add formatted annotations that are previewed in real-time. The tool also provides OCR functionality to automatically generate alt text for slides, and includes a template system to export the annotated presentation as HTML. 2 | 3 | -------------------------------------------------------------------------------- /apsw-query.docs.md: -------------------------------------------------------------------------------- 1 | This tool analyzes SQLite queries using the APSW (Another Python SQLite Wrapper) library. Enter your SQL query in the main text area or set up initial database schema in the collapsible section. You can add parameter values if your query uses parameterized statements. After clicking "Execute query," the tool provides detailed information about query execution plans, expanded SQL statements, and other diagnostic data to help you understand and optimize your SQLite queries. 2 | 3 | -------------------------------------------------------------------------------- /arena-animated.docs.md: -------------------------------------------------------------------------------- 1 | This interactive visualization displays the evolution of Elo ratings for large language models in the LMSYS Chatbot Arena over time. The chart shows how different models compare against each other, with higher positions indicating stronger performance. You can control the animation speed using the slider, pause/play the animation with the button, or upload custom JSON data with model ratings. The timeline at the bottom shows the current date being displayed as the animation progresses. 2 | 3 | -------------------------------------------------------------------------------- /ares.docs.md: -------------------------------------------------------------------------------- 1 | This converter transforms regular text into the ARES Phonetic Alphabet, commonly used in emergency communications. Enter text in the input field and click "Convert" to translate each letter and number into its corresponding phonetic code word (Alpha for A, Bravo for B, etc.). Spaces are marked as "(SPACE)" in the output to maintain clarity in the converted message. 2 | 3 | -------------------------------------------------------------------------------- /ares.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ARES Phonetic Alphabet Converter 7 | 63 | 64 | 65 |
66 |

ARES Phonetic Alphabet Converter

67 | 68 | 69 |
70 |
71 | 72 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /aria-live-regions.docs.md: -------------------------------------------------------------------------------- 1 | This demo page allows you to test how screen readers announce dynamic content changes through ARIA live regions. The interface lets you choose between "assertive" and "polite" announcement priorities, then test immediate or delayed notifications. The page includes detailed instructions for enabling and using VoiceOver on both macOS and iOS devices to experience how assistive technologies process these dynamic updates. 2 | 3 | -------------------------------------------------------------------------------- /aria-live-regions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Live region notification demo 7 | 94 | 95 | 96 |

Live region notification demo

97 | 98 |
99 |

Testing with VoiceOver on macOS

100 |
    101 |
  1. Press Command + F5 to turn on VoiceOver 102 |
      103 |
    • If using a Touch Bar MacBook, press the Touch ID button three times
    • 104 |
    • If prompted, click "Use VoiceOver" in the dialog
    • 105 |
    106 |
  2. 107 |
  3. Try clicking both buttons below to hear the notifications
  4. 108 |
  5. When finished, press Command + F5 again to turn off VoiceOver (or triple-press Touch ID)
  6. 109 |
110 |
111 | 112 |
113 |

Testing with VoiceOver on iOS

114 |
    115 |
  1. Go to Settings → Accessibility → VoiceOver
  2. 116 |
  3. Toggle VoiceOver on 117 |
      118 |
    • You can also add VoiceOver to Accessibility Shortcut: Settings → Accessibility → Accessibility Shortcut
    • 119 |
    • Then triple-click the side button to toggle VoiceOver
    • 120 |
    121 |
  4. 122 |
  5. Basic VoiceOver gestures: 123 |
      124 |
    • Tap once to select an item
    • 125 |
    • Double-tap anywhere to activate the selected item
    • 126 |
    • Swipe right/left with one finger to move to next/previous item
    • 127 |
    128 |
  6. 129 |
  7. Try using the buttons below to hear the notifications
  8. 130 |
  9. When finished, return to Settings → Accessibility → VoiceOver and toggle it off 131 |
      132 |
    • Or use your Accessibility Shortcut if configured
    • 133 |
    134 |
  10. 135 |
136 |
137 | 138 |

Demo

139 | 140 |
141 | 142 | 146 |
147 | 148 |
149 | 150 | 151 |
152 | 153 |
154 | 155 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /audio-spectrum.docs.md: -------------------------------------------------------------------------------- 1 | This Audio Spectrum Visualizer uses the Web Audio API to capture microphone input and display real-time frequency data. The application processes audio through an analyzer node and renders the spectrum as a series of colored bars on an HTML canvas, with the height and color of each bar representing different frequency amplitudes. Upon loading, the page requests microphone access and begins continuously updating the visualization. 2 | 3 | -------------------------------------------------------------------------------- /audio-spectrum.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Audio Spectrum Visualizer 7 | 20 | 21 | 22 | 23 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /avatar-web-component.docs.md: -------------------------------------------------------------------------------- 1 | The Avatar Web Component allows users to select, crop, and resize profile images directly in the browser. Users can upload images by clicking, drag-and-drop, or pasting from clipboard, then precisely crop the image using the interactive resizing handles. The component automatically maintains the proper aspect ratio, generates a JPEG preview, and saves the resulting image data to a specified form field. 2 | 3 | -------------------------------------------------------------------------------- /base64-gzip-decoder.docs.md: -------------------------------------------------------------------------------- 1 | This tool decodes base64 encoded gzip data. Paste your base64 encoded string into the input field and click the "Decode" button to convert it to plain text. The tool uses the pako JavaScript library to handle the gzip decompression after performing base64 decoding. 2 | 3 | -------------------------------------------------------------------------------- /base64-gzip-decoder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Base64 Gzip Decoder 7 | 70 | 71 | 72 |

Base64 Gzip decoder

73 | 74 |
75 |

Paste base64 encoded gzip data below:

76 | 77 | 78 |
79 | 80 |
81 |

Decoded result:

82 |
83 |
84 |
85 | 86 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /bash/README.md: -------------------------------------------------------------------------------- 1 | # Bash scripts 2 | 3 | These Bash scripts were written using LLMs. 4 | 5 | ## extract-file-history.sh 6 | 7 | [extract-file-history.sh](https://raw.githubusercontent.com/simonw/tools/refs/heads/main/bash/extract-file-history.sh) 8 | 9 | Create a brand new GitHub repository and copy the history of a single file from another repository into it. 10 | 11 | ```bash 12 | ./extract-file-history.sh path/to/repo file-path-within-repo.txt path/to/new-repo [optional-new-file-name] 13 | ``` 14 | 15 | I used it to create [this file history](https://github.com/simonw/llm-prices/commits/9a64678aa4635131dbb916ec99a735ee54050db1/) from [simonw/tools](https://github.com/simonw/tools): 16 | 17 | ```bash 18 | git clone https://github.com/simonw/tools 19 | ./extract-file-history.sh tools llm-prices.html llm-prices index.html 20 | ``` 21 | 22 | ## mem.sh 23 | 24 | [mem.sh](https://raw.githubusercontent.com/simonw/tools/refs/heads/main/bash/mem.sh) 25 | 26 | On macOS show the memory usage of the top processes, grouped by name. E.g. 27 | 28 | ```bash 29 | ./mem.sh 30 | ``` 31 | ``` 32 | 11.58 GB 167 Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin) 33 | 11.21 GB 54 Visual Studio Code.app/Contents/Frameworks/Code Helper (Renderer).app/Contents/MacOS/Code Helper (Renderer) 34 | 2.51 GB 56 Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper 35 | 2.10 GB 12 Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container 36 | 541.83 MB 1 Firefox.app/Contents/MacOS/firefox 37 | 456.03 MB 1 Visual Studio Code.app/Contents/MacOS/Electron 38 | ... 39 | ``` 40 | -------------------------------------------------------------------------------- /bash/extract-file-history.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # extract-file-history.sh 4 | # 5 | # Copy the full Git history of a single file into a brand-new repository. 6 | # 7 | # Usage: 8 | # ./extract-file-history.sh [output_filename] 9 | # Example: 10 | # ./extract-file-history.sh ~/projects/my-project src/utils/helper.js helper-history helper.js 11 | # 12 | # The new repository will contain one commit for every change ever made to the 13 | # specified file, preserving author, date and original commit message. If 14 | # output_filename is provided, the file in the new repo will be named accordingly. 15 | 16 | set -euo pipefail 17 | 18 | ############################################################################### 19 | # 1. Validate arguments & environment 20 | ############################################################################### 21 | if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then 22 | echo "Usage: $0 [output_filename]" 23 | exit 1 24 | fi 25 | 26 | SOURCE_REPO=$1 # e.g. /path/to/original/repo 27 | TARGET_FILE=$2 # e.g. src/utils/helper.js 28 | NEW_REPO_NAME=$3 # e.g. helper-history 29 | 30 | # Determine output filename in new repo 31 | if [ "$#" -eq 4 ]; then 32 | OUTPUT_FILENAME=$4 33 | else 34 | OUTPUT_FILENAME=$(basename "$TARGET_FILE") 35 | fi 36 | 37 | # Ensure Git is installed 38 | command -v git >/dev/null 2>&1 || { 39 | echo "Error: Git is not installed. Please install Git and try again." 40 | exit 1 41 | } 42 | 43 | # Ensure SOURCE_REPO is a Git repository 44 | if [ ! -d "$SOURCE_REPO/.git" ]; then 45 | echo "Error: $SOURCE_REPO is not a Git repository." 46 | exit 1 47 | fi 48 | 49 | # Ensure TARGET_FILE exists *somewhere* in the repo history 50 | if ! git -C "$SOURCE_REPO" ls-files --error-unmatch -- "$TARGET_FILE" >/dev/null 2>&1; then 51 | echo "Error: $TARGET_FILE does not exist in $SOURCE_REPO (at HEAD)." 52 | exit 1 53 | fi 54 | 55 | ############################################################################### 56 | # 2. Create the new repository 57 | ############################################################################### 58 | echo "Creating new repository: ${NEW_REPO_NAME}..." 59 | mkdir -p "${NEW_REPO_NAME}" 60 | cd "${NEW_REPO_NAME}" 61 | git init -q 62 | 63 | ############################################################################### 64 | # 3. Iterate over commits that touched the file (oldest → newest) 65 | ############################################################################### 66 | echo "Retrieving commit list for ${TARGET_FILE}..." 67 | COMMITS=$(git -C "$SOURCE_REPO" log --follow --reverse --pretty=%H -- "$TARGET_FILE") 68 | 69 | TOTAL=0 70 | for COMMIT in $COMMITS; do 71 | # Extract metadata (author, email, date) and commit message 72 | AUTHOR_NAME=$(git -C "$SOURCE_REPO" show -s --format=%an "$COMMIT") 73 | AUTHOR_EMAIL=$(git -C "$SOURCE_REPO" show -s --format=%ae "$COMMIT") 74 | COMMIT_DATE=$(git -C "$SOURCE_REPO" show -s --format=%ad "$COMMIT") 75 | COMMIT_MSG=$(git -C "$SOURCE_REPO" show -s --format=%B "$COMMIT") 76 | 77 | printf 'Importing %s ...\n' "$(git -C "$SOURCE_REPO" rev-parse --short "$COMMIT")" 78 | 79 | # Write the file content for that commit into the new repo 80 | if ! git -C "$SOURCE_REPO" show "$COMMIT":"$TARGET_FILE" > "$OUTPUT_FILENAME" 2>/dev/null; then 81 | echo "Warning: could not extract ${TARGET_FILE} at ${COMMIT} – skipping." 82 | continue 83 | fi 84 | 85 | git add "$OUTPUT_FILENAME" 86 | 87 | # Re-create the commit with original metadata 88 | GIT_AUTHOR_NAME="$AUTHOR_NAME" \ 89 | GIT_AUTHOR_EMAIL="$AUTHOR_EMAIL" \ 90 | GIT_AUTHOR_DATE="$COMMIT_DATE" \ 91 | GIT_COMMITTER_NAME="$AUTHOR_NAME" \ 92 | GIT_COMMITTER_EMAIL="$AUTHOR_EMAIL" \ 93 | GIT_COMMITTER_DATE="$COMMIT_DATE" \ 94 | git commit -q -m "$COMMIT_MSG" 95 | 96 | TOTAL=$((TOTAL + 1)) 97 | done 98 | 99 | ############################################################################### 100 | # 4. Done! 101 | ############################################################################### 102 | echo "Done! New repository '${NEW_REPO_NAME}' contains ${TOTAL} commit(s) of '${TARGET_FILE}' as '${OUTPUT_FILENAME}'." 103 | 104 | -------------------------------------------------------------------------------- /bash/mem.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # mem.sh — show per‐command RSS usage, defaults to top 20 but 4 | # pass --all to list all. 5 | 6 | # parse flag 7 | show_all=false 8 | if [[ "$1" == "--all" ]]; then 9 | show_all=true 10 | fi 11 | 12 | # the core pipeline, without sorting/limiting 13 | pipeline() { 14 | ps -xmo rss,comm | 15 | awk 'NR>1 { 16 | rss = $1 17 | $1 = "" # drop the rss field 18 | sub(/^ +/, "") # trim leading spaces 19 | cmd = $0 # full command, spaces intact 20 | mem[cmd] += rss 21 | count[cmd]++ 22 | } 23 | END { 24 | for (proc in mem) { 25 | display_name = proc 26 | if (proc ~ /^\/Applications\//) 27 | display_name = substr(proc, 15) 28 | 29 | memory_mb = mem[proc] / 1024 30 | if (memory_mb >= 1024) { 31 | memory_str = sprintf("%8.2f GB", memory_mb/1024) 32 | } else { 33 | memory_str = sprintf("%8.2f MB", memory_mb) 34 | } 35 | 36 | printf "%f\t%s\t%5d\t%s\n", 37 | memory_mb, memory_str, count[proc], display_name 38 | } 39 | }' 40 | } 41 | 42 | # run, then sort and optionally limit 43 | if $show_all; then 44 | pipeline | sort -nr | cut -f2- 45 | else 46 | pipeline | sort -nr | cut -f2- | head -n 20 47 | fi 48 | 49 | -------------------------------------------------------------------------------- /bbox-cropper.docs.md: -------------------------------------------------------------------------------- 1 | This bounding box tool allows you to draw selection boxes on images to get coordinates. Upload an image by pasting, dragging, or selecting a file, then draw and adjust your bounding box. The tool generates coordinates as percentages of the image dimensions, making them useful for responsive applications or when working with images of different sizes. 2 | 3 | -------------------------------------------------------------------------------- /bluesky-firehose.docs.md: -------------------------------------------------------------------------------- 1 | The Bluesky WebSocket Feed Monitor connects to the Bluesky social network's WebSocket API and displays real-time feed data in a log. Users can establish a connection, send custom JSON messages to filter the feed by specific collections or user DIDs, and view incoming messages in the output area. The interface provides controls to connect, disconnect, send messages, and clear the log history. 2 | 3 | -------------------------------------------------------------------------------- /bluesky-resolve.docs.md: -------------------------------------------------------------------------------- 1 | This tool resolves a Bluesky handle to its corresponding DID (Decentralized Identifier). Enter any Bluesky handle in the input field and click "Resolve" to retrieve the associated DID. The tool communicates with the Bluesky API and displays the result or an error message if the handle cannot be found. 2 | 3 | -------------------------------------------------------------------------------- /bluesky-resolve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 51 | 52 | 53 |

Resolve Bluesky handle to DID

54 | 55 |
56 | 57 | 58 |
59 | 60 |
61 | 62 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /bluesky-thread.docs.md: -------------------------------------------------------------------------------- 1 | The Bluesky Thread Viewer allows you to view and export conversation threads from Bluesky social network. Enter a Bluesky post URL to fetch the entire thread with replies, which will display in a hierarchical format with color-coded depth indicators. The tool provides options to copy the thread as formatted text or raw JSON data for sharing or analysis. 2 | 3 | -------------------------------------------------------------------------------- /bluesky-timeline.docs.md: -------------------------------------------------------------------------------- 1 | This web application allows you to view your Bluesky social media timeline by logging in with your username and app password. After authentication, it fetches your timeline and displays posts with avatars and content in a readable format. The timeline automatically refreshes every 10 seconds to show new content, and you can stop the auto-refresh at any time. 2 | 3 | -------------------------------------------------------------------------------- /box-shadow.docs.md: -------------------------------------------------------------------------------- 1 | This interactive tool allows you to create and customize CSS box shadows using simple slider controls. Adjust horizontal and vertical offsets, blur radius, spread radius, color, and opacity to generate the perfect shadow effect for your web elements. The tool provides a live preview of your shadow and displays the corresponding CSS code, which you can copy to your clipboard with a single click. 2 | 3 | -------------------------------------------------------------------------------- /broadcast-channel-chat.docs.md: -------------------------------------------------------------------------------- 1 | The Multi-Tab Chat application demonstrates real-time message synchronization across multiple browser tabs without requiring a backend server. Open the same page in several tabs to see messages instantly appear across all instances. The application utilizes the Broadcast Channel API to enable communication between tabs, with each tab receiving a unique identifier. Type messages in any tab and watch them propagate to all other open instances of the application. 2 | 3 | -------------------------------------------------------------------------------- /california-clock-change.docs.md: -------------------------------------------------------------------------------- 1 | This web application provides information about clock changes due to Daylight Saving Time in California (Pacific Time zone). The page displays the most recent time change, the upcoming change, current time status (PST or PDT), and offers helpful information about how the change might affect daily routines, including pet schedules. The application automatically detects if users are in the Pacific timezone and shows relevant timing details for both past and future clock adjustments. 2 | 3 | -------------------------------------------------------------------------------- /census-reporter-claude.docs.md: -------------------------------------------------------------------------------- 1 | The Census Explorer application provides an interface to explore American Community Survey data through the Census Reporter API. Users can search for geographic areas and specific census tables, then visualize the resulting data. The interface follows a clear three-step workflow: first search and select geographies and tables, then view the data in tabular or chart formats. Data can be toggled between different ACS releases and refreshed as needed. 2 | 3 | -------------------------------------------------------------------------------- /census-reporter-gemini.docs.md: -------------------------------------------------------------------------------- 1 | This tool helps you explore U.S. Census data through the Census Reporter API. Search for geographical areas like cities or counties, then select data tables related to demographics, income, or other census topics. Once you've selected both a geography and at least one data table, you can retrieve and view the census data with estimates and margins of error displayed in organized tables. 2 | 3 | -------------------------------------------------------------------------------- /chrome-prompt-playground.docs.md: -------------------------------------------------------------------------------- 1 | This playground allows you to run prompts against the Gemini Nano experimental model directly in Chrome Canary. Enter your prompt in the text area, click "Execute prompt" to see the generated response, and view your interaction history which is saved locally in your browser. The interface requires Chrome Canary with the "Prompt API for Gemini Nano" flag enabled. 2 | 3 | -------------------------------------------------------------------------------- /claude-token-counter.docs.md: -------------------------------------------------------------------------------- 1 | The Claude Token Counter allows you to calculate token usage for messages sent to Claude 3.5 Sonnet. Enter an optional system prompt and required user message, then add image or PDF files by dragging and dropping them into the designated area. After providing your Anthropic API key (stored locally in your browser), click "Count Tokens" to receive a detailed breakdown of token consumption for your inputs. 2 | 3 | -------------------------------------------------------------------------------- /click-grid-to-expand.docs.md: -------------------------------------------------------------------------------- 1 | This page displays an interactive CSS grid layout with a symmetrical animation effect. The grid contains four colored blocks of different sizes, with one block (colored green) that can be clicked to expand and cover the entire grid area. When expanded, clicking again triggers a smooth animation that returns the block to its original position and size. 2 | 3 | -------------------------------------------------------------------------------- /clipboard-viewer.docs.md: -------------------------------------------------------------------------------- 1 | The Clipboard Format Viewer allows you to inspect the raw data available when pasting content. When you paste text, images, files, or other content, the tool displays all available formats and their corresponding data in an organized view. Each format is shown in its own section, with special handling for images and files to provide visual previews where possible. 2 | 3 | -------------------------------------------------------------------------------- /code-with-claude-2025.docs.md: -------------------------------------------------------------------------------- 1 | This page allows users to download a conference schedule in ICS format for import into calendar applications. The page displays a preview of the ICS file containing 25 events from the San Francisco conference on May 22, 2025, including keynotes, workshops, panels, and other sessions. Users can download the complete schedule by clicking the download button at the top of the page. 2 | 3 | -------------------------------------------------------------------------------- /compare-pdfs.docs.md: -------------------------------------------------------------------------------- 1 | This PDF comparison tool enables side-by-side visual comparison of two PDF documents. Upload two PDFs by dragging them into the drop zone or clicking to select files. The tool renders each page of both documents and generates a third visualization that highlights differences between corresponding pages in red, making it easy to identify discrepancies between versions. 2 | 3 | -------------------------------------------------------------------------------- /css-text-wrapping.docs.md: -------------------------------------------------------------------------------- 1 | This guide provides a comprehensive overview of CSS text wrapping properties, including word-wrap/overflow-wrap, word-break, white-space, text-overflow, hyphens, line-break, and text-wrap. Each property is explained with multiple visual examples demonstrating different values, and includes tables summarizing the available options with their effects on text display. The page also includes browser compatibility information for each property and useful notes about implementation requirements. 2 | 3 | -------------------------------------------------------------------------------- /csv-marker-map.docs.md: -------------------------------------------------------------------------------- 1 | This page creates an interactive map with markers based on CSV data. It accepts parameters in the URL to set the center, zoom level, search query, individual markers, and a CSV file URL for bulk marker placement. The markers are displayed on an OpenStreetMap base layer, and the map view automatically updates the URL when panned or zoomed. 2 | 3 | -------------------------------------------------------------------------------- /csv-marker-map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CSV marker map - use ?csv=URL to CSV to populate 7 | 8 | 9 | 20 | 21 | 22 |
23 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /date-calculator.docs.md: -------------------------------------------------------------------------------- 1 | This Date Calculator tool enables users to calculate and visualize the time span between two dates. Enter a start and end date, and the calculator provides comprehensive information including total days, business days, weekend days, and weeks elapsed. The visual timeline displays the selected date range with a marker for the current date, allowing for easy visualization of time periods. 2 | 3 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest-playwright -------------------------------------------------------------------------------- /encrypt.docs.md: -------------------------------------------------------------------------------- 1 | This tool enables secure message encryption with a passphrase. Enter your message and a passphrase to generate an encrypted link that you can share with others. Recipients can decrypt the message by visiting the link and entering the correct passphrase. All encryption and decryption occurs in the browser using the Web Crypto API for enhanced security. 2 | 3 | -------------------------------------------------------------------------------- /escape-entities.docs.md: -------------------------------------------------------------------------------- 1 | This HTML Entity Escaper converts special characters in text to their corresponding HTML entities. The tool transforms characters like ampersands, angle brackets, and quotes into their HTML entity equivalents, making them safe to display on web pages without being interpreted as actual HTML code. After pasting text into the input box, the escaped version appears automatically in the output box for easy copying. 2 | 3 | -------------------------------------------------------------------------------- /escape-entities.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Entity Escaper 7 | 48 | 49 | 50 |

HTML Entity Escaper

51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 |

What this tool does:

59 |

This HTML Entity Escaper is a simple tool that helps you convert special characters in your text to their corresponding HTML entities. It's particularly useful when you need to display code or text containing HTML syntax on a web page without it being interpreted as actual HTML.

60 |

The tool escapes the following characters:

61 |
    62 |
  • & becomes &
  • 63 |
  • < becomes &lt;
  • 64 |
  • > becomes &gt;
  • 65 |
  • " becomes &quot;
  • 66 |
  • ' becomes &#39;
  • 67 |
68 |

To use the tool, simply paste your text into the input box. The escaped version will automatically appear in the output box. You can then copy the escaped text to your clipboard using the "Copy to clipboard" button.

69 |
70 | 71 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /event-planner.docs.md: -------------------------------------------------------------------------------- 1 | The Event Planner tool allows users to create events by entering details such as title, description, location, date, time, and timezone. After submitting the form, the application displays the event information, calculates the time remaining until the event, and provides a Google Calendar link for easy addition to your personal calendar. The tool supports multiple US timezones and formats the event details in a clear, readable format. 2 | 3 | -------------------------------------------------------------------------------- /event-planner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Event Planner 7 | 8 | 9 | 39 | 40 | 41 |

Event Planner

42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 67 | 68 | 69 |
70 | 71 |
72 | 73 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /exif.docs.md: -------------------------------------------------------------------------------- 1 | The EXIF Data Viewer allows you to extract and view metadata from your digital photos. Upload any image to display embedded EXIF information including GPS coordinates (latitude and longitude) if available. The tool presents both specific location data and a complete breakdown of all metadata tags contained within the image file. 2 | 3 | -------------------------------------------------------------------------------- /exif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EXIF Data Viewer 7 | 40 | 41 | 42 |
43 |

EXIF Data Viewer

44 | 45 |
46 |
47 |
48 | 49 | 50 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /extract-urls.docs.md: -------------------------------------------------------------------------------- 1 | This tool extracts URLs from copied web page content. Paste HTML content into the editable area, and the tool automatically parses out all hyperlinks and displays them in a clean list format. The extracted URLs can be copied to your clipboard with a single click for use in other applications. 2 | 3 | -------------------------------------------------------------------------------- /extract-urls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Extract URLs 7 | 51 | 52 | 53 |

Extract URLs

54 |

Copy content from a web page and paste here to extract linked URLs:

55 |
56 |
57 |

Extracted

58 | 59 | 60 |
61 | 62 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonw/tools/baa3dfb4f2ba57a7d807721baa3c5ddde3924d6a/favicon.ico -------------------------------------------------------------------------------- /flexbox-playground.docs.md: -------------------------------------------------------------------------------- 1 | The CSS Flexbox Playground provides an interactive environment for experimenting with flexbox layout properties. Users can adjust container settings like flex-direction, justify-content, and align-items through the control panel, and modify individual flex item properties such as flex-grow and align-self by selecting specific items. The playground displays real-time visual results and generates the corresponding CSS code, making it a practical tool for learning and testing flexbox layouts. 2 | 3 | -------------------------------------------------------------------------------- /footnotes-experiment.docs.md: -------------------------------------------------------------------------------- 1 | This page demonstrates an interactive footnote system that enhances content by providing additional information without disrupting reading flow. When users hover over or click a footnote marker, a popup appears with the footnote text, eliminating the need to scroll to the bottom of the page. The system includes accessibility features with proper ARIA roles and supports both desktop and mobile interactions through hover and click events. 2 | 3 | -------------------------------------------------------------------------------- /gather_links.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import os 4 | import re 5 | import subprocess 6 | from pathlib import Path 7 | from datetime import datetime 8 | 9 | 10 | def get_file_commit_details(file_path): 11 | """ 12 | Get the commit details for a specific file. 13 | Returns a list of dictionaries with hash, message, and date. 14 | """ 15 | try: 16 | # Get each commit as a separate record with its hash, date, and message 17 | result = subprocess.run( 18 | ["git", "log", "--format=%H|%aI|%B%x00", "--", file_path], 19 | capture_output=True, 20 | text=True, 21 | check=True, 22 | ) 23 | 24 | commits = [] 25 | # Split by NULL character which safely separates commits 26 | raw_commits = result.stdout.strip().split("\0") 27 | 28 | for raw_commit in raw_commits: 29 | if not raw_commit.strip(): 30 | continue 31 | 32 | # Find the first pipe to extract commit hash 33 | first_pipe = raw_commit.find("|") 34 | if first_pipe == -1: 35 | continue 36 | 37 | commit_hash = raw_commit[:first_pipe] 38 | 39 | # Find the second pipe to extract date 40 | second_pipe = raw_commit.find("|", first_pipe + 1) 41 | if second_pipe == -1: 42 | continue 43 | 44 | commit_date = raw_commit[first_pipe + 1 : second_pipe] 45 | 46 | # The rest is the commit message 47 | commit_message = raw_commit[second_pipe + 1 :] 48 | 49 | commits.append( 50 | {"hash": commit_hash, "date": commit_date, "message": commit_message} 51 | ) 52 | 53 | return commits 54 | except subprocess.CalledProcessError as e: 55 | print(f"Error getting commit history for {file_path}: {e}") 56 | return [] 57 | 58 | 59 | def extract_urls(text): 60 | """ 61 | Extract URLs from text using regex pattern. 62 | Returns a list of URLs. 63 | """ 64 | # Pattern for URLs that captures the full URL until whitespace or end of string 65 | url_pattern = r"(https?://[^\s]+)" 66 | return re.findall(url_pattern, text) 67 | 68 | 69 | def main(): 70 | # Get current directory 71 | current_dir = Path.cwd() 72 | 73 | # Find all HTML files 74 | html_files = list(current_dir.glob("*.html")) 75 | 76 | # Dictionary to store results 77 | results = {"pages": {}} 78 | 79 | # Process each HTML file 80 | for html_file in html_files: 81 | file_name = html_file.name 82 | print(f"Processing {file_name}...") 83 | 84 | # Get commit details for this file 85 | commits = get_file_commit_details(html_file) 86 | 87 | if not commits: 88 | continue 89 | 90 | # Extract URLs from commit messages 91 | all_urls = [] 92 | for commit in commits: 93 | urls = extract_urls(commit["message"]) 94 | all_urls.extend(urls) 95 | 96 | # Remove duplicates but preserve order 97 | unique_urls = [] 98 | for url in all_urls: 99 | if url not in unique_urls: 100 | unique_urls.append(url) 101 | 102 | # Add to results if any commits were found 103 | if commits: 104 | results["pages"][file_name] = {"commits": commits, "urls": unique_urls} 105 | 106 | # Save results to JSON file 107 | with open("gathered_links.json", "w") as f: 108 | json.dump(results, f, indent=2) 109 | 110 | print(f"Processed {len(html_files)} files") 111 | print(f"Found details for {len(results['pages'])} files") 112 | print("Results saved to gathered_links.json") 113 | 114 | 115 | if __name__ == "__main__": 116 | main() 117 | -------------------------------------------------------------------------------- /gemini-bbox-tool.docs.md: -------------------------------------------------------------------------------- 1 | This page implements an automatic redirect to the "/gemini-bbox" location. When a user visits this page, their browser will immediately redirect them to the new location, with a fallback text link provided if the automatic redirection fails to work. 2 | 3 | -------------------------------------------------------------------------------- /gemini-bbox-tool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redirecting to /gemini-bbox 7 | 8 | 9 |

If you are not redirected automatically, follow this link to /gemini-bbox.

10 | 11 | 12 | -------------------------------------------------------------------------------- /gemini-bbox.docs.md: -------------------------------------------------------------------------------- 1 | This tool visualizes bounding box coordinates from the Gemini API on images. Upload an image, enter a prompt asking for bounding box coordinates in the format [ymin, xmin, ymax, xmax], and the application will display the image with color-coded bounding boxes overlaid. The canvas includes a coordinate grid system with axes labeled from 0-1000, and individual cropped sections of each detected object are displayed below the main visualization. 2 | 3 | -------------------------------------------------------------------------------- /gemini-chat.docs.md: -------------------------------------------------------------------------------- 1 | This web application allows you to interact with Google's Gemini AI models through a simple chat interface. Users can select different Gemini model versions, send messages, and receive responses in real-time with markdown rendering support. The app requires a Gemini API key on first use and displays usage metadata and response time for each interaction. 2 | 3 | -------------------------------------------------------------------------------- /gemini-image-json.docs.md: -------------------------------------------------------------------------------- 1 | This tool renders JSON output from Gemini's image generation API for better visualization. Paste your Gemini API response JSON in the text area and click "Render JSON" to display images and text in a readable format. The page also extracts and displays usage metadata and model version information from the JSON, making it easier to interpret the response data. 2 | 3 | -------------------------------------------------------------------------------- /gemini-mask.docs.md: -------------------------------------------------------------------------------- 1 | This tool visualizes image segmentation masks generated by the Gemini API. Upload an image, enter a prompt requesting segmentation masks, and the application will display the results with bounding boxes overlaid on your image. The extracted masks are shown side-by-side with the corresponding image regions, and a coordinate system helps you understand the normalized coordinates used in the API response. 2 | 3 | -------------------------------------------------------------------------------- /github-api-write.docs.md: -------------------------------------------------------------------------------- 1 | This tool allows you to upload files or images directly to GitHub repositories using the GitHub API. Enter your GitHub token, repository details, and file path, then choose between text content or image upload. The application handles the base64 encoding required by GitHub's API and provides a link to view your file once successfully uploaded. 2 | 3 | -------------------------------------------------------------------------------- /github-issue-to-markdown.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts GitHub issues into markdown format for easy reference and sharing. Enter a GitHub issue URL, click "Convert to markdown," and the tool will fetch the issue content including all comments and format it as markdown. The converted markdown can be copied to your clipboard with a single click for use in documentation or other contexts. 2 | 3 | -------------------------------------------------------------------------------- /github-issue.docs.md: -------------------------------------------------------------------------------- 1 | This page allows you to fetch and convert GitHub issues to markdown format. Enter a GitHub issue URL in the input field and click "Fetch Issue" to retrieve the issue content including comments. You can provide a personal access token for accessing private repositories or to avoid rate limiting, which will be saved in your browser for future use. 2 | 3 | -------------------------------------------------------------------------------- /gpt-4o-audio-player.docs.md: -------------------------------------------------------------------------------- 1 | The Gist Audio Player allows you to play and download audio from GitHub Gists containing OpenAI GPT-4o audio responses. Enter a Gist URL containing base64-encoded WAV data from the GPT-4o-audio-preview model, and the player will extract the audio, display the transcript, and provide playback controls. You can also share specific audio by using the URL parameter format `?gist=GIST_ID`. 2 | 3 | -------------------------------------------------------------------------------- /hacker-news-thread-export.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts Hacker News threads into a structured text format. Enter a Hacker News post ID or URL in the input field, then click "Fetch and format" to retrieve the thread. The tool organizes comments in a hierarchical format with path numbers (like [1.2.3]) indicating the reply structure, making it easy to track conversation flow and export threads for reference elsewhere. 2 | 3 | -------------------------------------------------------------------------------- /haiku.docs.md: -------------------------------------------------------------------------------- 1 | This webpage enables you to generate haikus based on images captured from your device's camera. After granting camera access, capture an image using the center button, and Claude will analyze the image and generate a haiku inspired by what it sees. The interface allows switching between front and rear cameras if your device has multiple cameras available. 2 | 3 | -------------------------------------------------------------------------------- /html-preview.docs.md: -------------------------------------------------------------------------------- 1 | This HTML Live Preview tool allows you to write HTML code and immediately see the rendered results. Type your HTML in the editor on the left side, and the preview will update on the right side (or toggle between views on mobile devices). The tool includes features to copy your HTML code to the clipboard and supports responsive layouts that adapt to different screen sizes. 2 | 3 | -------------------------------------------------------------------------------- /huggingface-storage.docs.md: -------------------------------------------------------------------------------- 1 | This tool checks the storage size of any Hugging Face model. Enter a model URL or path (like "mlx-community/Llama-3.2-3B-Instruct-4bit"), and the application will fetch and display the model's size in MB or GB. The tool accepts both full Hugging Face URLs and direct model paths as input. 2 | 3 | -------------------------------------------------------------------------------- /iframe-api-explorer.docs.md: -------------------------------------------------------------------------------- 1 | The API Explorer provides a sandboxed interface for testing APIs through a secure iframe implementation. Users can enter an API URL and view the JSON response without security risks, as the parent page handles the actual fetch requests through a message-passing protocol. The interface demonstrates secure cross-frame communication using `postMessage()` while maintaining appropriate isolation between contexts. 2 | 3 | -------------------------------------------------------------------------------- /iframe-sandbox.docs.md: -------------------------------------------------------------------------------- 1 | This page provides an interactive sandbox for testing HTML and JavaScript code with customizable iframe security controls. It features a code editor on the left where you can write HTML, and a preview pane on the right that renders your code within an iframe. The sandbox controls beneath the preview allow you to enable or disable specific security restrictions such as scripts, forms, popups, and same-origin policy, letting you experiment with different security contexts. 2 | 3 | -------------------------------------------------------------------------------- /image-resize-quality.docs.md: -------------------------------------------------------------------------------- 1 | This tool allows you to compare image quality and filesize at different compression levels. Upload an image by dragging, clicking, or pasting it into the interface. The tool will display versions of your image at original and half size with various quality settings (100%, 90%, 70%, 50%, and 30%), showing file size for each variation. For transparent images, a background color picker lets you choose the fill color. 2 | 3 | -------------------------------------------------------------------------------- /image-to-jpeg.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts images to JPEG format with adjustable quality. Users can upload an image by clicking on the drop zone or dragging and dropping a file, then adjust the JPEG compression quality using the slider. The converted image is displayed with its data URI and approximate file size, allowing users to find the optimal balance between image quality and file size. 2 | 3 | -------------------------------------------------------------------------------- /image-to-jpeg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Drag and Drop Image 5 | 29 | 30 | 31 |
Click or drag and drop an image here
32 | 33 |
34 |
35 | 36 |
37 | Converted Image
38 |
39 |
40 | 41 |
42 |
JPEG Size: 0 bytes
43 | 44 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /image-to-svg.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts JPG or PNG images to SVG format using the imagetracerjs library. You can upload an image by dragging and dropping it onto the designated area or by clicking to browse your files. After conversion, the SVG appears on screen and its code is displayed in a text field below, which can be copied to the clipboard with a single click. 2 | 3 | -------------------------------------------------------------------------------- /image-to-svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image to SVG 7 | 49 | 50 | 51 |

Image to SVG

52 |

Using imagetracerjs by András Jankovics.

53 |
54 |

Drag and drop a JPG or PNG image here, or click to select a file

55 |
56 | 57 |
58 |

SVG Code:

59 | 60 | 61 | 62 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /incomplete-json-printer.docs.md: -------------------------------------------------------------------------------- 1 | The Incomplete JSON Pretty Printer formats truncated or incomplete JSON data into a readable structure. It features automatic formatting as you type, copy to clipboard functionality, and a sample example to demonstrate its capabilities. The tool helps developers visualize and work with partial JSON data by applying proper indentation and structure to make the content more readable. 2 | 3 | -------------------------------------------------------------------------------- /jina-embeddings-image-token-calculator.docs.md: -------------------------------------------------------------------------------- 1 | The Image Token Calculator allows you to estimate the token cost for processing images with AI models. Upload an image by dragging and dropping it onto the designated area or by clicking to browse your files. After uploading, the calculator will display the image dimensions, number of tiles required to process the image, and the total token cost based on a calculation of 1000 tokens per tile. 2 | 3 | -------------------------------------------------------------------------------- /jina-embeddings-image-token-calculator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Token Calculator 7 | 35 | 36 | 37 |

Image Token Calculator

38 |
39 |

Drop an image here or click to select

40 |
41 | 42 |
43 | 44 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /jina-reader.docs.md: -------------------------------------------------------------------------------- 1 | Jina Reader provides a web interface to view and process online content using the Jina Reader API. Enter a URL to fetch its content in various formats (Markdown, HTML, Text, or LLM Markdown), and the result will be displayed in both raw and rendered forms. You can also run Claude AI analysis on the fetched content using customizable prompts to generate summaries or other text transformations. 2 | 3 | -------------------------------------------------------------------------------- /json-schema-builder.docs.md: -------------------------------------------------------------------------------- 1 | The JSON Schema Builder allows you to create JSON schemas with a visual interface. Add properties by specifying their name, type, and whether they're required, then build nested objects and arrays. Your schema is displayed in real-time and can be copied to the clipboard with a single click. The tool also automatically saves your work in the URL, enabling you to share your schema with others. 2 | 3 | -------------------------------------------------------------------------------- /json-to-yaml.docs.md: -------------------------------------------------------------------------------- 1 | This converter transforms JSON to YAML in three different formats: block style (standard indentation), flow style (compact representation), and quoted strings (all string values enclosed in double quotes). Simply paste your JSON into the input field and the tool automatically generates the corresponding YAML formats, with copy buttons for each output style. 2 | 3 | -------------------------------------------------------------------------------- /json-to-yaml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON to YAML Converter 7 | 8 | 79 | 80 | 81 |
82 | 83 | 84 |
85 |
86 | 87 |
88 |
89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 |
98 | 99 | 100 |
101 | 102 |
103 | 104 |
105 |
106 | 107 | 108 |
109 | 110 |
111 |
112 | 113 | 196 | 197 | -------------------------------------------------------------------------------- /keyboard-filters.docs.md: -------------------------------------------------------------------------------- 1 | The Filter Badge Component allows users to create and manage data filtering criteria through an interactive interface. Users can add filters by clicking the "Add filter" button, then select a column, operator, and value to define each filter. Each part of the filter is keyboard accessible, with support for tab navigation, Enter/Space key actions, and direct value editing through a popover interface. 2 | 3 | -------------------------------------------------------------------------------- /lightning-timer.docs.md: -------------------------------------------------------------------------------- 1 | The Lightning Timer is a customizable countdown application designed for time-sensitive presentations or tasks. Set your preferred duration and optional warning time through the settings gear icon. Click the timer to start, pause, or reset it. The screen changes color as time runs low, providing a visual cue when your allotted time is nearly exhausted. 2 | 3 | -------------------------------------------------------------------------------- /link-temp.docs.md: -------------------------------------------------------------------------------- 1 | This code implements a Gemini model class for the LLM library, enabling interactions with Google's Gemini AI models. It handles configuration settings like API keys, model selection, and temperature, while providing methods to generate text completions and embeddings through the Google Generative AI API. The implementation includes proper error handling and parameter validation. 2 | 3 | -------------------------------------------------------------------------------- /link-temp.html: -------------------------------------------------------------------------------- 1 | link 2 | -------------------------------------------------------------------------------- /llm-prices.docs.md: -------------------------------------------------------------------------------- 1 | This HTML page implements an automatic redirect to the website www.llm-prices.com. The page uses a meta refresh tag with a zero-second delay to immediately redirect the visitor upon loading. For visitors whose browsers don't support automatic redirects or have them disabled, a fallback text message with a clickable link is provided. 2 | 3 | -------------------------------------------------------------------------------- /llm-prices.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redirecting to llm-prices.com 7 | 8 | 9 |

If you are not redirected automatically, follow this link to www.llm-prices.com/.

10 | 11 | 12 | -------------------------------------------------------------------------------- /markdown-math.docs.md: -------------------------------------------------------------------------------- 1 | This interactive tool renders Markdown and LaTeX math formulas in real-time. As you type in the text area, the content is converted to formatted HTML in the preview pane, supporting both inline math expressions ($...$) and block math equations ($$...$$). The generated HTML can be copied to clipboard for use in other applications or websites. 2 | 3 | -------------------------------------------------------------------------------- /markdown-math.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Markdown and Math Live Renderer 7 | 8 | 9 | 10 | 11 | 49 | 50 | 51 |

Markdown and Math Live Renderer

52 | 80 |

Preview:

81 |
82 |

Generated HTML:

83 | 84 | 85 | 86 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /mask-visualizer.docs.md: -------------------------------------------------------------------------------- 1 | The Mask Visualizer is a tool for rendering JSON data containing image masks with bounding boxes in a configurable coordinate system. Users can paste JSON that includes box coordinates and base64-encoded mask images, then select one of four coordinate system origins (Top Left, Top Right, Bottom Left, or Bottom Right). The visualization displays the masks with colored bounding boxes and provides detailed coordinate information for both original and transformed coordinates. 2 | 3 | -------------------------------------------------------------------------------- /mdn-timelines.docs.md: -------------------------------------------------------------------------------- 1 | This web application displays timeline views of browser support for web APIs based on MDN's browser compatibility data. Users can search for specific APIs by name, view when different browsers added support for those APIs, and access detailed information including MDN documentation links, specifications, and status indicators. The interface shows a chronological timeline of browser support with version numbers and release dates, along with information about browsers that don't support the selected API. 2 | 3 | -------------------------------------------------------------------------------- /nav-for-headings.docs.md: -------------------------------------------------------------------------------- 1 | This tool processes HTML content by adding unique ID attributes to all header elements (h1-h6) that don't already have them. After processing, it generates a list of anchor links pointing to each header, using the page URL you provide combined with the header IDs as fragment identifiers. The processed HTML and a formatted list of header links are displayed in separate output fields. 2 | 3 | -------------------------------------------------------------------------------- /nav-for-headings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Header Processor 7 | 57 | 58 | 59 |
60 | 61 | 62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 |
70 | 71 |
72 | 73 |
74 | 75 | 76 |
77 | 78 |
79 | 80 | 81 |
82 | 83 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ocr.docs.md: -------------------------------------------------------------------------------- 1 | This tool performs Optical Character Recognition (OCR) on PDF documents and images directly in your web browser. Upload a file by dragging and dropping or clicking the dropzone, and the text content will be extracted using Tesseract.js. For PDFs, each page is processed separately and compiled into a full document, with support for multiple languages. 2 | 3 | -------------------------------------------------------------------------------- /openai-audio-output.docs.md: -------------------------------------------------------------------------------- 1 | This page allows you to generate human-like speech from text using OpenAI's GPT-4o audio models. Enter your prompts, select a model and voice, then generate audio output which can be played, downloaded, or shared via GitHub Gist. The interface displays both the audio player and a transcript of the spoken content, along with the complete API response for reference. 2 | 3 | -------------------------------------------------------------------------------- /openai-audio.docs.md: -------------------------------------------------------------------------------- 1 | This page provides an interface for recording audio, which can then be submitted to OpenAI's GPT-4o audio model along with a text prompt. The application allows users to record speech through their microphone, review the recording, and submit it to the API for processing. After submission, the page displays both the formatted response and detailed information about token usage and associated costs. 2 | 3 | -------------------------------------------------------------------------------- /openai-webrtc.docs.md: -------------------------------------------------------------------------------- 1 | This interface connects to OpenAI's real-time audio API using WebRTC technology. Users can start an audio session by providing their OpenAI API token and selecting a voice option, allowing for voice-based interactions with GPT-4o. The page displays detailed statistics about each interaction including token usage and estimated costs, while also maintaining a running log of all session events. 2 | 3 | -------------------------------------------------------------------------------- /openfreemap-demo.docs.md: -------------------------------------------------------------------------------- 1 | This demonstration showcases MapLibre GL integration with OpenFreeMap, displaying 1000 randomly generated points within San Francisco. The code provides three different marker implementation options: scaled markers using MapLibre's built-in functionality, custom HTML elements styled as circular markers, and a more performant circle layer using GeoJSON data. The map initializes with a tilted perspective and automatically adjusts its bounds to encompass all generated markers. 2 | 3 | -------------------------------------------------------------------------------- /openfreemap-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MapLibre GL + OpenFreeMap demo 5 | 6 | 7 | 13 | 14 | 15 |
16 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /passkeys.docs.md: -------------------------------------------------------------------------------- 1 | This page provides a demonstration environment for testing and understanding passkeys. Users can register new passkeys, authenticate with existing ones, and manage their stored credentials. The demo operates entirely in the browser using localStorage for credential storage, with a debugging section that shows technical details of credential creation and authentication responses. 2 | 3 | -------------------------------------------------------------------------------- /paste-html-subset.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts rich text into a clean HTML subset by letting you paste formatted content from any source. Paste text into the editable area to generate sanitized HTML code that only includes supported elements like paragraphs, headings, links, and lists. The tool provides both a code view of the resulting HTML and a live preview window to verify the output appearance. 2 | 3 | -------------------------------------------------------------------------------- /paste-rich-text.docs.md: -------------------------------------------------------------------------------- 1 | This tool allows you to extract HTML code from formatted text copied from websites. Paste content into the editable area to see the underlying HTML code displayed in a dedicated output field. The extracted HTML can be copied to your clipboard with a single click and a preview shows how the content will render when used in another webpage. 2 | 3 | -------------------------------------------------------------------------------- /paste-rich-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rich Text HTML Extractor 7 | 91 | 92 | 93 |

Rich text HTML extractor

94 |

Copy formatted text from a webpage and paste here to extract the HTML:

95 | 96 |
97 |

Paste using Ctrl+V or ⌘+V to capture the rich text HTML.

98 | 99 |
100 |

HTML code

101 | 102 | 103 | 104 | 105 | 106 |
107 |

Preview

108 |
109 |
110 |
111 | 112 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /pdf-ocr.docs.md: -------------------------------------------------------------------------------- 1 | This page serves as an automatic redirect to the OCR application. It uses the HTML meta refresh tag to immediately forward visitors to the /ocr URL, while also providing a fallback link for users whose browsers don't support automatic redirects or have disabled this feature. 2 | 3 | -------------------------------------------------------------------------------- /pdf-ocr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirecting... 6 | 7 | 8 | 9 |

If you are not redirected automatically, follow this link to OCR.

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /php-deserializer.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts PHP serialized data to JSON format. Paste serialized PHP data in the input field and it will automatically be converted to formatted JSON, which you can then copy to your clipboard with a single click. If there are any errors in the deserialization process, the tool will display a helpful error message. 2 | 3 | -------------------------------------------------------------------------------- /php-deserializer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PHP deserializer 7 | 68 | 69 | 70 |

PHP deserializer

71 |

Paste serialized PHP data below to convert it to JSON

72 | 73 | 74 | 75 |
76 | 77 |
78 | 79 | 80 |
81 | 82 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /pipfile.docs.md: -------------------------------------------------------------------------------- 1 | This tool extracts and formats Python dependencies from a Pipfile.lock file. Paste your Pipfile.lock JSON content into the text area and click "Parse Dependencies" to convert it into both Pipfile format and requirements.txt format. You can copy either format to your clipboard using the designated buttons. 2 | 3 | -------------------------------------------------------------------------------- /pomodoro.docs.md: -------------------------------------------------------------------------------- 1 | The Pomodoro Timer is a productivity tool that helps you manage work sessions using timed intervals. Enter your goal, select a duration (5-60 minutes), and track your progress with detailed session logs. The application records your session history including start/end times, durations, and any pauses, storing this data locally for future reference. 2 | 3 | -------------------------------------------------------------------------------- /progress.docs.md: -------------------------------------------------------------------------------- 1 | This page tracks the progress of the current U.S. presidential term with a visual progress bar and statistical information. It displays days elapsed, days remaining, and the percentage of the term completed, while also tracking the time until midterm elections. The progress bar includes a marker showing when midterms occur during the presidential term, with additional statistics about the days until midterms and how far through the first half of the term we currently are. 2 | 3 | -------------------------------------------------------------------------------- /prompts-js.docs.md: -------------------------------------------------------------------------------- 1 | Prompts.js is a JavaScript library that provides modern alternatives to the browser's built-in alert, confirm, and prompt dialogs. The library offers an async/await syntax allowing for cleaner code when working with user interactions. Demonstrations on this page show how to trigger different types of dialogs, with the results displayed in a dedicated area below the buttons. 2 | 3 | -------------------------------------------------------------------------------- /prompts-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Prompts.js 5 | 6 | 39 | 40 | 41 | 42 |

Prompts.js

43 |

A lightweight JavaScript library for creating modal dialogs with alert, confirm, and prompt functionalities.

44 |
45 | await Prompts.alert("This is an alert message!");
46 | const resultBoolean = await Prompts.confirm("Do you want to proceed?");
47 | const name = await Prompts.prompt("What is your name?");
48 | 
49 | 50 |
51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /python/extract_har.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.12" 3 | # dependencies = [ 4 | # "click", 5 | # ] 6 | # /// 7 | 8 | import json 9 | import zipfile 10 | from pathlib import Path 11 | import click 12 | import mimetypes 13 | from urllib.parse import urlparse 14 | 15 | 16 | def get_extension_for_mimetype(mimetype): 17 | """Get the most common file extension for a given MIME type.""" 18 | ext = mimetypes.guess_extension(mimetype) 19 | if ext: 20 | return ext 21 | 22 | # Fallback mappings for common types 23 | fallbacks = { 24 | "application/json": ".json", 25 | "image/svg+xml": ".svg", 26 | "text/html": ".html", 27 | "text/css": ".css", 28 | "application/javascript": ".js", 29 | } 30 | return fallbacks.get(mimetype, ".bin") 31 | 32 | 33 | def extract_path_from_url(url): 34 | """Convert a URL into a filesystem path, preserving the path structure.""" 35 | parsed = urlparse(url) 36 | path = parsed.path.lstrip("/") 37 | 38 | # Handle empty paths 39 | if not path: 40 | path = "index" 41 | 42 | # Remove trailing slashes 43 | path = path.rstrip("/") 44 | 45 | return path 46 | 47 | 48 | @click.command() 49 | @click.argument("harzip", type=click.Path(exists=True)) 50 | @click.argument("mimetypes", nargs=-1, required=True) 51 | @click.option( 52 | "-o", 53 | "--output", 54 | type=click.Path(), 55 | default=".", 56 | help="Output directory for extracted files", 57 | ) 58 | @click.option( 59 | "--paths", 60 | is_flag=True, 61 | help="Use URL paths for filenames instead of original names", 62 | ) 63 | @click.option( 64 | "--pretty-json", 65 | is_flag=True, 66 | help="Pretty print JSON files with 2-space indentation", 67 | ) 68 | def extract_har(harzip, mimetypes, output, paths, pretty_json): 69 | """Extract files of specified MIME types from a HAR archive.""" 70 | output_dir = Path(output) 71 | output_dir.mkdir(parents=True, exist_ok=True) 72 | 73 | with zipfile.ZipFile(harzip) as zf: 74 | # Read the HAR JSON file 75 | try: 76 | har_content = json.loads(zf.read("har.har")) 77 | except KeyError: 78 | click.echo("Error: har.har not found in archive", err=True) 79 | return 80 | except json.JSONDecodeError: 81 | click.echo("Error: Invalid JSON in har.har", err=True) 82 | return 83 | 84 | # Process each entry 85 | for entry in har_content.get("log", {}).get("entries", []): 86 | response = entry.get("response", {}) 87 | content = response.get("content", {}) 88 | 89 | # Check if this entry matches our MIME type filter 90 | if content.get("mimeType") not in mimetypes: 91 | continue 92 | 93 | # Get the file reference and URL 94 | file_ref = content.get("_file") 95 | if not file_ref: 96 | continue 97 | 98 | request_url = entry.get("request", {}).get("url", "") 99 | 100 | try: 101 | # Extract the file 102 | file_content = zf.read(file_ref) 103 | 104 | if paths: 105 | # Use URL path for filename 106 | path = extract_path_from_url(request_url) 107 | # Add appropriate extension if not present 108 | if not Path(path).suffix: 109 | path += get_extension_for_mimetype(content["mimeType"]) 110 | outpath = output_dir / path 111 | else: 112 | # Use original filename 113 | outpath = output_dir / file_ref 114 | 115 | # Ensure parent directories exist 116 | outpath.parent.mkdir(parents=True, exist_ok=True) 117 | 118 | # Handle JSON pretty printing if requested 119 | if pretty_json and content["mimeType"] == "application/json": 120 | try: 121 | json_data = json.loads(file_content) 122 | file_content = json.dumps(json_data, indent=2).encode("utf-8") 123 | except json.JSONDecodeError: 124 | click.echo( 125 | f"Warning: Could not pretty print {outpath} - invalid JSON", 126 | err=True, 127 | ) 128 | 129 | # Write the file 130 | outpath.write_bytes(file_content) 131 | click.echo(f"Extracted: {outpath}") 132 | 133 | except KeyError: 134 | click.echo(f"Warning: File {file_ref} not found in archive", err=True) 135 | continue 136 | 137 | 138 | if __name__ == "__main__": 139 | extract_har() 140 | -------------------------------------------------------------------------------- /python/extract_sourcemap.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.12" 3 | # dependencies = [ 4 | # "click", 5 | # ] 6 | # /// 7 | 8 | import re 9 | import base64 10 | import sys 11 | import click 12 | 13 | 14 | @click.command() 15 | @click.argument("file_path", type=click.Path(exists=True)) 16 | def extract_sourcemap(file_path): 17 | """ 18 | Extract and decode a base64-encoded source map from a JavaScript file. 19 | 20 | This tool searches for sourceMappingURL with base64-encoded data and outputs 21 | the decoded content to stdout. 22 | """ 23 | try: 24 | # Read the JavaScript file 25 | with open(file_path, "r", encoding="utf-8") as f: 26 | content = f.read() 27 | 28 | # Regular expression to find the sourceMappingURL with base64 data 29 | pattern = r"//# sourceMappingURL=data:application/json;base64,([a-zA-Z0-9+/=]+)" 30 | match = re.search(pattern, content) 31 | 32 | if match: 33 | # Extract the base64 encoded part 34 | base64_data = match.group(1) 35 | 36 | # Decode the base64 data 37 | decoded_data = base64.b64decode(base64_data).decode("utf-8") 38 | 39 | # Print the decoded content to stdout 40 | click.echo(decoded_data) 41 | 42 | return 0 43 | else: 44 | click.echo("No source map data found in the file.", err=True) 45 | return 1 46 | 47 | except Exception as e: 48 | click.echo(f"Error: {str(e)}", err=True) 49 | return 1 50 | 51 | 52 | if __name__ == "__main__": 53 | sys.exit(extract_sourcemap()) 54 | -------------------------------------------------------------------------------- /python/gguf_inspect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | gguf_inspect.py – Dump ALL metadata key/value pairs from a GGUF file. 4 | 5 | • Default output: YAML (each value in a literal block scalar). 6 | • --json Pretty-print JSON instead. 7 | • --exclude PREFIX Skip keys that begin with PREFIX. May be repeated. 8 | 9 | Example: 10 | ./gguf_dump_meta.py llama.gguf # YAML 11 | ./gguf_dump_meta.py --json llama.gguf # JSON 12 | ./gguf_dump_meta.py --exclude tokenizer.ggml. llama.gguf # YAML, filtered 13 | """ 14 | 15 | import argparse 16 | import json 17 | import struct 18 | import sys 19 | from pathlib import Path 20 | from typing import Any, Dict, List 21 | 22 | # ────────────────────────────────────────────────────── 23 | # gguf helpers 24 | # ────────────────────────────────────────────────────── 25 | 26 | _PRIM_FMT = { 27 | 0: "B", 1: "b", 28 | 2: "H", 3: "h", 29 | 4: "I", 5: "i", 30 | 6: "f", 31 | 7: "B", # BOOL 32 | 10: "Q", 11: "q", 33 | 12: "d", 34 | } 35 | _PRIM_SIZE = {k: struct.calcsize(v) for k, v in _PRIM_FMT.items()} 36 | 37 | STRING, ARRAY = 8, 9 38 | 39 | 40 | def _read(fmt: str, fh): 41 | size = struct.calcsize(fmt) 42 | data = fh.read(size) 43 | if len(data) != size: 44 | raise EOFError("unexpected EOF") 45 | return struct.unpack("<" + fmt, data) 46 | 47 | 48 | def _read_string(fh) -> str: 49 | (length,) = _read("Q", fh) 50 | raw = fh.read(length) 51 | if len(raw) != length: 52 | raise EOFError("unexpected EOF in string") 53 | return raw.decode("utf-8", errors="replace") 54 | 55 | 56 | def _read_scalar(typ: int, fh): 57 | (val,) = _read(_PRIM_FMT[typ], fh) 58 | return bool(val) if typ == 7 else val 59 | 60 | 61 | def _read_array(elem_type: int, count: int, fh) -> List[Any]: 62 | if elem_type == STRING: 63 | return [_read_string(fh) for _ in range(count)] 64 | 65 | elem_size = _PRIM_SIZE[elem_type] 66 | data = fh.read(elem_size * count) 67 | if len(data) != elem_size * count: 68 | raise EOFError("unexpected EOF in array") 69 | fmt = f"<{count}{_PRIM_FMT[elem_type]}" 70 | out = list(struct.unpack(fmt, data)) 71 | if elem_type == 7: 72 | out = [bool(v) for v in out] 73 | return out 74 | 75 | 76 | def extract_metadata(path: Path) -> Dict[str, Any]: 77 | meta: Dict[str, Any] = {} 78 | with path.open("rb") as fh: 79 | if fh.read(4) != b"GGUF": 80 | raise ValueError("not a GGUF file") 81 | _version, = _read("I", fh) 82 | _ntensors, n_kv = _read("QQ", fh) 83 | 84 | for _ in range(n_kv): 85 | key = _read_string(fh) 86 | (val_type,) = _read("I", fh) 87 | 88 | if val_type == STRING: 89 | value = _read_string(fh) 90 | elif val_type == ARRAY: 91 | elem_type, = _read("I", fh) 92 | (count,) = _read("Q", fh) 93 | value = _read_array(elem_type, count, fh) 94 | else: 95 | value = _read_scalar(val_type, fh) 96 | 97 | meta[key] = value 98 | return meta 99 | 100 | 101 | # ────────────────────────────────────────────────────── 102 | # Output helpers 103 | # ────────────────────────────────────────────────────── 104 | 105 | def dump_yaml(meta: Dict[str, Any]) -> None: 106 | for key in sorted(meta): 107 | print(f"{key}: |") 108 | lines = json.dumps(meta[key], ensure_ascii=False, indent=2).splitlines() 109 | for ln in lines or [""]: 110 | print(f" {ln}") 111 | print() # blank line between keys 112 | 113 | 114 | def dump_json(meta: Dict[str, Any]) -> None: 115 | print(json.dumps(meta, ensure_ascii=False, indent=2)) 116 | 117 | 118 | # ────────────────────────────────────────────────────── 119 | # CLI 120 | # ────────────────────────────────────────────────────── 121 | 122 | def parse_args(): 123 | p = argparse.ArgumentParser( 124 | description="Dump metadata key/value pairs from a GGUF file." 125 | ) 126 | p.add_argument( 127 | "--json", 128 | action="store_true", 129 | help="output as pretty-printed JSON instead of YAML", 130 | ) 131 | p.add_argument( 132 | "--exclude", 133 | action="append", 134 | default=[], 135 | metavar="PREFIX", 136 | help="exclude keys that start with PREFIX (can be given multiple times)", 137 | ) 138 | p.add_argument("gguf", metavar="MODEL.GGUF", help="path to GGUF file") 139 | return p.parse_args() 140 | 141 | 142 | def should_include(key: str, prefixes: List[str]) -> bool: 143 | return not any(key.startswith(pref) for pref in prefixes) 144 | 145 | 146 | def main(): 147 | args = parse_args() 148 | 149 | path = Path(args.gguf) 150 | if not path.is_file(): 151 | sys.exit(f"File not found: {path}") 152 | 153 | meta = extract_metadata(path) 154 | if args.exclude: 155 | meta = {k: v for k, v in meta.items() if should_include(k, args.exclude)} 156 | 157 | if args.json: 158 | dump_json(meta) 159 | else: 160 | dump_yaml(meta) 161 | 162 | 163 | if __name__ == "__main__": 164 | main() 165 | -------------------------------------------------------------------------------- /python/highlight.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.9" 3 | # dependencies = [ 4 | # "click", 5 | # ] 6 | # /// 7 | 8 | import sys 9 | import re 10 | from dataclasses import dataclass 11 | from typing import List, Union, Tuple 12 | import click 13 | 14 | 15 | @dataclass 16 | class Text: 17 | """Represents non-matching text""" 18 | 19 | content: str 20 | 21 | 22 | @dataclass 23 | class Match: 24 | """Represents matching text that should be highlighted""" 25 | 26 | content: str 27 | 28 | 29 | @dataclass 30 | class Ellipsis: 31 | """Represents ... for truncated content""" 32 | 33 | pass 34 | 35 | 36 | def find_matches( 37 | text: str, terms: List[str], context: int = 30 38 | ) -> List[Union[Text, Match, Ellipsis]]: 39 | """ 40 | Find all matches of the given terms in the text, with surrounding context. 41 | Returns a list of Text, Match, and Ellipsis objects. 42 | """ 43 | if not terms: 44 | return [Text(text)] 45 | 46 | # Create pattern that matches any of the terms 47 | pattern = "|".join(re.escape(term) for term in terms) 48 | regex = re.compile(pattern, re.IGNORECASE) 49 | 50 | # Find all matches with their positions 51 | matches = list(regex.finditer(text)) 52 | if not matches: 53 | return [Text(text)] 54 | 55 | # Create segments with context 56 | segments: List[Tuple[int, int]] = [] # List of (start, end) for context regions 57 | match_positions: List[Tuple[int, int]] = ( 58 | [] 59 | ) # List of (start, end) for actual matches 60 | 61 | for match in matches: 62 | start, end = match.span() 63 | match_positions.append((start, end)) 64 | context_start = max(0, start - context) 65 | context_end = min(len(text), end + context) 66 | segments.append((context_start, context_end)) 67 | 68 | # Merge overlapping context segments 69 | merged_segments = [] 70 | current = segments[0] 71 | for next_seg in segments[1:]: 72 | if next_seg[0] <= current[1]: 73 | # Segments overlap, merge them 74 | current = (current[0], max(current[1], next_seg[1])) 75 | else: 76 | merged_segments.append(current) 77 | current = next_seg 78 | merged_segments.append(current) 79 | 80 | # Convert segments to result objects 81 | result = [] 82 | last_end = 0 83 | 84 | for seg_start, seg_end in merged_segments: 85 | # Add ellipsis if there's a gap 86 | if seg_start > last_end: 87 | if last_end > 0: # Only add if not at the start 88 | result.append(Ellipsis()) 89 | 90 | # Find all matches within this segment 91 | segment_matches = [ 92 | (s, e) for s, e in match_positions if s >= seg_start and e <= seg_end 93 | ] 94 | 95 | # Add content with proper highlighting 96 | pos = seg_start 97 | for match_start, match_end in segment_matches: 98 | # Add text before match 99 | if match_start > pos: 100 | result.append(Text(text[pos:match_start])) 101 | # Add match 102 | result.append(Match(text[match_start:match_end])) 103 | pos = match_end 104 | 105 | # Add remaining text in segment 106 | if pos < seg_end: 107 | result.append(Text(text[pos:seg_end])) 108 | 109 | last_end = seg_end 110 | 111 | # Add final ellipsis if we're not at the end 112 | if last_end < len(text): 113 | result.append(Ellipsis()) 114 | 115 | return result 116 | 117 | 118 | @click.command() 119 | @click.argument("terms", nargs=-1, required=True) 120 | @click.option( 121 | "-c", "--context", default=30, help="Number of context characters around matches" 122 | ) 123 | def main(terms: List[str], context: int): 124 | """Search for terms in stdin and output colored matches with context.""" 125 | text = sys.stdin.read() 126 | results = find_matches(text, terms, context) 127 | 128 | # ANSI color codes 129 | YELLOW = "\033[93m" 130 | RED = "\033[91m" 131 | END = "\033[0m" 132 | 133 | # Convert results to colored output 134 | for item in results: 135 | if isinstance(item, Text): 136 | print(item.content, end="") 137 | elif isinstance(item, Match): 138 | print(f"{RED}{item.content}{END}", end="") 139 | elif isinstance(item, Ellipsis): 140 | print(f"{YELLOW}...{END}", end="") 141 | print() # Final newline 142 | 143 | 144 | if __name__ == "__main__": 145 | main() 146 | -------------------------------------------------------------------------------- /python/json_extractor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import sys 4 | import argparse 5 | 6 | 7 | def extract_json_objects(text): 8 | """ 9 | Extract all valid JSON objects from text using a state machine parser. 10 | 11 | Returns a list of valid JSON objects as Python dictionaries. 12 | """ 13 | result = [] 14 | i = 0 15 | text_length = len(text) 16 | 17 | while i < text_length: 18 | # Look for the start of a potential JSON object 19 | if text[i] == "{": 20 | # Try to parse a complete JSON object starting at this position 21 | potential_json, end_pos = parse_json_object(text, i) 22 | if potential_json is not None: 23 | try: 24 | # Directly parse without fixing - invalid JSON will be rejected 25 | parsed_obj = json.loads(potential_json) 26 | result.append(parsed_obj) 27 | except json.JSONDecodeError: 28 | pass 29 | # Skip to the end of this object to avoid finding nested objects 30 | i = end_pos 31 | else: 32 | i += 1 33 | else: 34 | i += 1 35 | 36 | return result 37 | 38 | 39 | def parse_json_object(text, start_pos): 40 | """ 41 | Parse a JSON object starting at start_pos. 42 | 43 | Returns (json_string, end_position) if successful, or (None, start_pos) if not. 44 | """ 45 | i = start_pos 46 | if i >= len(text) or text[i] != "{": 47 | return None, start_pos 48 | 49 | # State variables 50 | brace_count = 1 # We already found the first '{' 51 | in_string = False 52 | escape_next = False 53 | 54 | # Start accumulating the JSON string 55 | json_str = text[i] 56 | i += 1 57 | 58 | while i < len(text) and brace_count > 0: 59 | char = text[i] 60 | json_str += char 61 | 62 | # Handle string state 63 | if in_string: 64 | if escape_next: 65 | escape_next = False 66 | elif char == "\\": 67 | escape_next = True 68 | elif char == '"': 69 | in_string = False 70 | else: 71 | # Only count braces outside of strings 72 | if char == '"': 73 | in_string = True 74 | elif char == "{": 75 | brace_count += 1 76 | elif char == "}": 77 | brace_count -= 1 78 | # If we've closed all braces, we have a potential complete JSON object 79 | if brace_count == 0: 80 | return json_str, i + 1 81 | 82 | i += 1 83 | 84 | # If we got here, we didn't find a complete JSON object 85 | return None, start_pos 86 | 87 | 88 | def main(): 89 | # Parse command line arguments 90 | parser = argparse.ArgumentParser(description="Extract JSON objects from text.") 91 | parser.add_argument( 92 | "file", 93 | nargs="?", 94 | type=str, 95 | default=None, 96 | help="File to read (defaults to stdin)", 97 | ) 98 | parser.add_argument( 99 | "--all", 100 | "-a", 101 | action="store_true", 102 | help="Output all JSON objects found, not just the best one", 103 | ) 104 | args = parser.parse_args() 105 | 106 | # Read the input 107 | if args.file: 108 | try: 109 | with open(args.file, "r") as f: 110 | text = f.read() 111 | except Exception as e: 112 | print(f"Error reading file {args.file}: {e}", file=sys.stderr) 113 | sys.exit(1) 114 | else: 115 | text = sys.stdin.read() 116 | 117 | # Extract JSON objects 118 | json_objects = extract_json_objects(text) 119 | 120 | # Output the results 121 | if not json_objects: 122 | print("No valid JSON objects found.", file=sys.stderr) 123 | sys.exit(1) 124 | 125 | if args.all: 126 | # Output all objects 127 | output = json_objects 128 | else: 129 | # Output the best (most complex) object 130 | output = max(json_objects, key=lambda x: len(json.dumps(x))) 131 | 132 | # Always print with 2-space indent 133 | print(json.dumps(output, indent=2)) 134 | 135 | 136 | if __name__ == "__main__": 137 | main() 138 | -------------------------------------------------------------------------------- /python/whitespace_cleaner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # /// script 3 | # requires-python = ">=3.12" 4 | # /// 5 | 6 | import os 7 | import sys 8 | import argparse 9 | from pathlib import Path 10 | 11 | 12 | def is_text_file(file_path): 13 | """ 14 | Check if a file is a text file by attempting to read it as UTF-8. 15 | """ 16 | try: 17 | with open(file_path, "r", encoding="utf-8") as f: 18 | f.read(1024) # Try reading a small chunk 19 | return True 20 | except UnicodeDecodeError: 21 | return False 22 | except Exception: 23 | return False 24 | 25 | 26 | def process_file(file_path, dry_run=False): 27 | """ 28 | Process a single file, replacing lines with only whitespace with empty lines. 29 | Returns a tuple of (file_path, changes_made, lines_changed) 30 | """ 31 | if not is_text_file(file_path): 32 | return file_path, False, 0 33 | 34 | try: 35 | with open(file_path, "r", encoding="utf-8") as f: 36 | lines = f.readlines() 37 | 38 | changes_needed = False 39 | lines_changed = 0 40 | new_lines = [] 41 | 42 | for line in lines: 43 | # Check if the line contains only whitespace characters (spaces, tabs) 44 | # but is not already just a newline 45 | if line.strip() == "" and line != "\n": 46 | new_lines.append("\n") 47 | changes_needed = True 48 | lines_changed += 1 49 | else: 50 | new_lines.append(line) 51 | 52 | if changes_needed and not dry_run: 53 | with open(file_path, "w", encoding="utf-8") as f: 54 | f.writelines(new_lines) 55 | 56 | return file_path, changes_needed, lines_changed 57 | 58 | except Exception as e: 59 | print(f"Error processing {file_path}: {e}", file=sys.stderr) 60 | return file_path, False, 0 61 | 62 | 63 | def main(): 64 | parser = argparse.ArgumentParser( 65 | description="Replace whitespace-only lines with blank lines in text files." 66 | ) 67 | parser.add_argument( 68 | "paths", nargs="+", help="One or more files or directories to process" 69 | ) 70 | parser.add_argument( 71 | "--dry-run", 72 | action="store_true", 73 | help="Show what would be changed without making changes", 74 | ) 75 | 76 | args = parser.parse_args() 77 | 78 | total_files_processed = 0 79 | total_files_changed = 0 80 | total_lines_changed = 0 81 | 82 | for path_str in args.paths: 83 | path = Path(path_str) 84 | 85 | if not path.exists(): 86 | print(f"Path does not exist: {path}", file=sys.stderr) 87 | continue 88 | 89 | if path.is_file(): 90 | # Process a single file 91 | filepath, changed, lines = process_file(path, args.dry_run) 92 | total_files_processed += 1 93 | if changed: 94 | total_files_changed += 1 95 | total_lines_changed += lines 96 | action = "Would replace" if args.dry_run else "Replaced" 97 | print(f"{action} {lines} whitespace-only line(s) in {filepath}") 98 | 99 | elif path.is_dir(): 100 | # Recursively process a directory 101 | for root, _, files in os.walk(path): 102 | for file in files: 103 | filepath = os.path.join(root, file) 104 | filepath_obj = Path(filepath) 105 | 106 | # Skip hidden files and directories 107 | if filepath_obj.name.startswith(".") or any( 108 | part.startswith(".") for part in filepath_obj.parts 109 | ): 110 | continue 111 | 112 | filepath, changed, lines = process_file(filepath, args.dry_run) 113 | total_files_processed += 1 114 | if changed: 115 | total_files_changed += 1 116 | total_lines_changed += lines 117 | action = "Would replace" if args.dry_run else "Replaced" 118 | print(f"{action} {lines} whitespace-only line(s) in {filepath}") 119 | 120 | # Print summary 121 | print(f"\nSummary:") 122 | print(f"Files processed: {total_files_processed}") 123 | 124 | if args.dry_run: 125 | print(f"Files that would be changed: {total_files_changed}") 126 | print(f"Lines that would be changed: {total_lines_changed}") 127 | else: 128 | print(f"Files changed: {total_files_changed}") 129 | print(f"Lines changed: {total_lines_changed}") 130 | 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /qr.docs.md: -------------------------------------------------------------------------------- 1 | This QR Code Decoder allows you to extract text and links from QR code images. Upload an image file, drag and drop it onto the designated area, or paste an image from your clipboard. The tool automatically processes the image and displays the decoded content, converting URLs into clickable links for easy access. 2 | 3 | -------------------------------------------------------------------------------- /render-claude-citations.docs.md: -------------------------------------------------------------------------------- 1 | This tool renders JSON responses from Claude API that contain citations. Paste a JSON response into the text area and click "Render message" to display the message with proper formatting and citation blockquotes. The renderer uses a sandboxed iframe for security, ensuring the content is displayed safely while maintaining proper styling and organization of the text and cited passages. 2 | 3 | -------------------------------------------------------------------------------- /render-markdown.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts Markdown to HTML using GitHub's API. Enter your Markdown text in the input area, click "Render" to see it transformed into HTML with proper formatting. You can customize the output by choosing between standard Markdown or GitHub Flavored Markdown, and optionally clean up the HTML to remove hidden elements. The rendered HTML can be copied to the clipboard. 2 | 3 | -------------------------------------------------------------------------------- /rtf-to-html.docs.md: -------------------------------------------------------------------------------- 1 | This tool inspects and converts Rich Text Format (RTF) content from your clipboard into HTML with color formatting preserved. When you paste RTF text using keyboard shortcuts, the application parses the RTF color table, applies appropriate color styling to text segments, and displays both a visual preview and the generated HTML markup for you to copy. 2 | 3 | -------------------------------------------------------------------------------- /rtf-to-html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Clipboard RTF to HTML Inspector 7 | 21 | 22 | 23 |

Clipboard RTF to HTML Inspector

24 |
25 | 26 |

Press Cmd+V (Mac) or Ctrl+V (Windows) to paste and convert RTF to colorized HTML.

27 |
28 |
29 | 30 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /schema-dsl.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts simple schema definitions written in a compact DSL format into JSON Schema. Enter field definitions with types and descriptions in the text area, and the converter generates the appropriate JSON Schema in real-time. Toggle the "Array items schema" checkbox to wrap your schema in an array structure, and use the "Load example" button to see how the syntax works. 2 | 3 | -------------------------------------------------------------------------------- /side-panel-dialog.docs.md: -------------------------------------------------------------------------------- 1 | This page displays a product catalog with a side panel dialog system. When users click on a product, details appear in a side panel implemented using the HTML `` element. The panel can operate in two modes: non-modal (allowing interaction with the main page) or modal (blocking interaction until closed). Users can also submit feedback about products through a form in the side panel. 2 | 3 | -------------------------------------------------------------------------------- /social-media-cropper.docs.md: -------------------------------------------------------------------------------- 1 | The Social Media Card Cropper allows you to create customized social media cards with correct aspect ratios for different platforms. Upload images by dropping, selecting, or pasting from clipboard, then crop and adjust your image with the interactive controls. Choose from preset aspect ratios for Twitter/LinkedIn, Facebook, Instagram, or Substack, adjust the background color, and download your finished card in the optimal dimensions for your selected platform. 2 | 3 | -------------------------------------------------------------------------------- /species-observation-map.docs.md: -------------------------------------------------------------------------------- 1 | The Species observation map allows you to search for wildlife sightings around the world using data from iNaturalist. Search for a species by name, specify how far back in time to look, and view observations plotted on an interactive map with clustered markers. Each marker provides detailed information about the observation including date, location, observer name, and photos when available. 2 | 3 | -------------------------------------------------------------------------------- /sql-pretty-printer.docs.md: -------------------------------------------------------------------------------- 1 | This SQL Pretty Printer tool formats SQL queries according to your preferences. You can customize the dialect, tab width, keyword casing, and indentation style to match your project requirements. The tool displays a formatted version of your SQL in real-time as you type, and provides a copy button for easily transferring the formatted query to your clipboard. 2 | 3 | -------------------------------------------------------------------------------- /sqlite-wasm.docs.md: -------------------------------------------------------------------------------- 1 | This interactive tool allows users to query a SQLite database of pelican sightings in Half Moon Bay. The application loads a WASM-based SQLite engine in the browser and initializes a sample database with pelican observation data. Users can modify and execute SQL queries directly in the browser to explore different pelican species, locations, and sighting counts. 2 | 3 | -------------------------------------------------------------------------------- /svg-progressive-render.docs.md: -------------------------------------------------------------------------------- 1 | This tool allows you to visualize the progressive rendering of SVG files. Paste your SVG code into the top text area, set a duration, and click "Render" to see the SVG build gradually character by character. You can also use the live editor to make real-time changes and see them reflected immediately in the preview area below. 2 | 3 | -------------------------------------------------------------------------------- /svg-render.docs.md: -------------------------------------------------------------------------------- 1 | This tool converts SVG images to JPEG or PNG format. Upload an SVG file or paste SVG code directly, then customize output settings including dimensions, background color, padding, and transparency. The converter generates a downloadable image and provides a base64-encoded image tag that can be copied for use in web projects. 2 | 3 | -------------------------------------------------------------------------------- /svg-sandbox.docs.md: -------------------------------------------------------------------------------- 1 | This demonstration shows how to embed SVG graphics directly in HTML using Base64 encoding. The page displays three example SVGs encoded as data URIs within image tags, along with their decoded source code below. Base64 embedding allows SVG graphics to be included without external file dependencies, while the browser's security model prevents any embedded JavaScript from executing when SVGs are loaded as images. 2 | 3 | -------------------------------------------------------------------------------- /swagger-subset.docs.md: -------------------------------------------------------------------------------- 1 | This tool enables you to create a subset of your Swagger API documentation by selecting specific endpoints. Paste your Swagger JSON, choose the desired paths and methods, and generate a new Swagger document containing only those selected endpoints. The tool automatically includes all referenced schema definitions to ensure your subset documentation remains valid and complete. 2 | 3 | -------------------------------------------------------------------------------- /tests/ocr-test-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonw/tools/baa3dfb4f2ba57a7d807721baa3c5ddde3924d6a/tests/ocr-test-text.png -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest-playwright -------------------------------------------------------------------------------- /tests/test_ocr.py: -------------------------------------------------------------------------------- 1 | from http.client import HTTPConnection 2 | import pathlib 3 | from playwright.sync_api import Page, expect 4 | import pytest 5 | from subprocess import Popen, PIPE 6 | import time 7 | 8 | 9 | test_dir = pathlib.Path(__file__).parent.absolute() 10 | root = test_dir.parent.absolute() 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def static_server(): 15 | process = Popen( 16 | ["python", "-m", "http.server", "8123", "--directory", root], stdout=PIPE 17 | ) 18 | retries = 5 19 | while retries > 0: 20 | conn = HTTPConnection("127.0.0.1:8123") 21 | try: 22 | conn.request("HEAD", "/") 23 | response = conn.getresponse() 24 | if response is not None: 25 | yield process 26 | break 27 | except ConnectionRefusedError: 28 | time.sleep(1) 29 | retries -= 1 30 | 31 | if not retries: 32 | raise RuntimeError("Failed to start http server") 33 | else: 34 | process.terminate() 35 | process.wait() 36 | 37 | 38 | def test_initial_state(page: Page, static_server): 39 | page.goto("http://127.0.0.1:8123/ocr.html") 40 | expect(page.locator("h1")).to_have_text( 41 | "OCR PDFs and images directly in your browser" 42 | ) 43 | expect(page.locator("#dropzone")).to_have_text( 44 | "Drag and drop a PDF, JPG, PNG, or GIF file here or click to select a file" 45 | ) 46 | expect(page.locator("#fullDocumentSection")).not_to_be_visible() 47 | expect(page.locator("#id_language")).to_have_value("eng") 48 | 49 | 50 | def test_open_image(page: Page, static_server): 51 | page.goto("http://127.0.0.1:8123/ocr.html") 52 | file_input = page.locator("#fileInput") 53 | file_input.set_input_files(str(test_dir / "ocr-test-text.png")) 54 | expect(page.locator(".image-container img")).to_be_visible() 55 | expect(page.locator(".textarea-alt")).to_have_attribute( 56 | "placeholder", "OCRing image..." 57 | ) 58 | expect(page.locator(".textarea-alt")).to_have_value("OCR test text") 59 | expect(page.locator("#fullDocumentSection")).not_to_be_visible() 60 | 61 | 62 | def test_open_pdf(page: Page, static_server): 63 | page.goto("http://127.0.0.1:8123/ocr.html") 64 | page.locator("#fileInput").set_input_files(str(test_dir / "three_page_pdf.pdf")) 65 | expect(page.locator(".image-container img")).to_have_count(3) 66 | expect(page.locator(".textarea-alt")).to_have_count(3) 67 | expect(page.locator(".textarea-alt").nth(0)).to_have_value("Page 1") 68 | expect(page.locator(".textarea-alt").nth(1)).to_have_value("Second page") 69 | expect(page.locator(".textarea-alt").nth(2)).to_have_value("Page the third") 70 | expect(page.locator("#fullDocumentSection")).to_be_visible() 71 | expect(page.locator("#fullDocument")).to_have_value( 72 | "Page 1\n\nSecond page\n\nPage the third" 73 | ) 74 | -------------------------------------------------------------------------------- /tests/three_page_pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonw/tools/baa3dfb4f2ba57a7d807721baa3c5ddde3924d6a/tests/three_page_pdf.pdf -------------------------------------------------------------------------------- /text-wrap-balance-nav.docs.md: -------------------------------------------------------------------------------- 1 | This interactive demo allows you to experiment with the `text-wrap: balance` CSS property on a navigation bar. You can control how many navigation items are displayed using the slider and toggle the text-wrap balance feature on and off to observe how it affects the distribution of navigation links across multiple lines. The navigation bar has a fixed width of 400px to demonstrate wrapping behavior. 2 | 3 | -------------------------------------------------------------------------------- /text-wrap-balance-nav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Navigation Bar with Text-Wrap Balance 7 | 50 | 51 | 52 | 70 | 71 |
72 |
73 | 74 | 75 | 13 76 |
77 |
78 | 81 |
82 |
83 | 84 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /tiff-orientation.docs.md: -------------------------------------------------------------------------------- 1 | This tool analyzes JPEG images to determine their TIFF orientation metadata. Upload an image by dragging and dropping or clicking to select a file, and the application will extract and display the orientation information. The tool parses the EXIF header to find the orientation tag and shows both the orientation value and a human-readable description of how the image is oriented. 2 | 3 | -------------------------------------------------------------------------------- /timezones.docs.md: -------------------------------------------------------------------------------- 1 | This Timezone Meeting Planner allows you to compare time differences between two timezones. Select locations from the dropdown menus to generate a 48-hour comparison table showing the corresponding times in both locations. The tool updates the URL with your selections so you can share specific timezone comparisons with others. 2 | 3 | -------------------------------------------------------------------------------- /timezones.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Timezone Meeting Planner 7 | 62 | 63 | 64 |
65 |
66 |
First Timezone
67 | 73 |
74 |
75 |
Second Timezone
76 | 82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
UTC-timeFirst LocationSecond Location
98 | 99 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /token-usage.docs.md: -------------------------------------------------------------------------------- 1 | The Token Usage Calculator analyzes token consumption across different LLM API calls. Paste YAML-formatted data from the `llm logs -su` command to view a breakdown of input and output tokens grouped by model name. The calculator automatically processes your data and displays total token usage statistics for each model detected in your logs. 2 | 3 | -------------------------------------------------------------------------------- /transfer-time.docs.md: -------------------------------------------------------------------------------- 1 | The Transfer Time Calculator helps you estimate how long it will take to upload or download files. Enter your file size (in GB, MB, or TB) and the transfer speed (in MB/s, Mbps, or KB/s), and the calculator will display the estimated time in days, hours, minutes, and seconds. The result includes a detailed breakdown of the calculation so you can understand exactly how the time was determined. 2 | 3 | -------------------------------------------------------------------------------- /unix-timestamp.docs.md: -------------------------------------------------------------------------------- 1 | The Timestamp Converter tool transforms Unix timestamps into human-readable date and time formats. Enter a Unix timestamp (in seconds or milliseconds) in the input field to see the corresponding UTC and local time displayed below. The converter automatically initializes with the current timestamp and updates the displayed times as you type. 2 | 3 | -------------------------------------------------------------------------------- /user-agent.docs.md: -------------------------------------------------------------------------------- 1 | This page displays the User Agent string of your current browser. The User Agent contains information about your browser, operating system, and device, which websites can use to identify how to properly deliver content to you. 2 | 3 | -------------------------------------------------------------------------------- /user-agent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User Agent 7 | 8 | 9 |

Your User Agent:

10 |

11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /vercel/anthropic-proxy/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /vercel/anthropic-proxy/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const { createProxyMiddleware } = require('http-proxy-middleware'); 4 | 5 | const app = express(); 6 | 7 | app.use(cors({origin: 'https://tools.simonwillison.net'})); 8 | 9 | // Proxy middleware 10 | const apiProxy = createProxyMiddleware({ 11 | target: 'https://api.anthropic.com', 12 | changeOrigin: true, 13 | pathRewrite: { 14 | '^/v1': '/v1', 15 | }, 16 | }); 17 | 18 | // Use the proxy middleware for /v1/* routes 19 | app.use('/v1', apiProxy); 20 | 21 | module.exports = app; 22 | -------------------------------------------------------------------------------- /vercel/anthropic-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anthropic-proxy", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "express": "^4.19.1", 15 | "http-proxy-middleware": "^2.0.6" 16 | } 17 | } -------------------------------------------------------------------------------- /vercel/anthropic-proxy/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "index.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "(.*)", 12 | "dest": "index.js" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /word-counter.docs.md: -------------------------------------------------------------------------------- 1 | This text editor allows you to create and manage multiple writing sections with real-time word and character counts. Each section auto-saves your content to local storage after one second of inactivity, ensuring your work is preserved between sessions. You can add new sections or remove existing ones as needed, with all changes automatically backed up. 2 | 3 | -------------------------------------------------------------------------------- /write_docs.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.12" 3 | # /// 4 | 5 | import os 6 | import subprocess 7 | import glob 8 | import re 9 | import argparse 10 | from pathlib import Path 11 | 12 | 13 | def get_current_commit_hash(file_path): 14 | """Get the most recent commit hash for a specific file.""" 15 | try: 16 | result = subprocess.run( 17 | ["git", "log", "-n", "1", "--pretty=format:%H", "--", file_path], 18 | capture_output=True, 19 | text=True, 20 | check=True, 21 | ) 22 | return result.stdout.strip() 23 | except subprocess.CalledProcessError: 24 | return None 25 | 26 | 27 | def extract_commit_hash_from_docs(docs_file_path): 28 | """Extract the commit hash from a documentation file if it exists.""" 29 | if not os.path.exists(docs_file_path): 30 | return None 31 | 32 | with open(docs_file_path, "r", encoding="utf-8") as f: 33 | content = f.read() 34 | 35 | hash_match = re.search(r"", content) 36 | if hash_match: 37 | return hash_match.group(1) 38 | 39 | return None 40 | 41 | 42 | def generate_documentation(html_file_path): 43 | """Generate documentation for an HTML file using Claude.""" 44 | try: 45 | result = subprocess.run( 46 | f"cat '{html_file_path}' | llm -m claude-3.7-sonnet --system \"Write a paragraph of documentation for this page as markdown. Do not include a heading. Do not use words like just or simply. Keep it to 2-3 sentences.\"", 47 | shell=True, 48 | capture_output=True, 49 | text=True, 50 | check=True, 51 | ) 52 | return result.stdout.strip() 53 | except subprocess.CalledProcessError as e: 54 | print(f"Error generating documentation for {html_file_path}: {e}") 55 | return None 56 | 57 | 58 | def main(): 59 | parser = argparse.ArgumentParser( 60 | description="Generate documentation for HTML files in a repository" 61 | ) 62 | parser.add_argument("--path", default=".", help="Path to the repository") 63 | parser.add_argument( 64 | "--verbose", "-v", action="store_true", help="Enable verbose output" 65 | ) 66 | parser.add_argument( 67 | "--dry-run", 68 | action="store_true", 69 | help="Show what would be done without making changes", 70 | ) 71 | args = parser.parse_args() 72 | 73 | # Find all HTML files in the repository 74 | html_files = glob.glob(f"{args.path}/**/*.html", recursive=True) 75 | 76 | if args.verbose: 77 | print(f"Found {len(html_files)} HTML files") 78 | 79 | updated_count = 0 80 | skipped_count = 0 81 | 82 | for html_file in html_files: 83 | html_path = Path(html_file) 84 | docs_file = html_path.with_suffix(".docs.md") 85 | 86 | if args.verbose: 87 | print(f"Processing {html_file}") 88 | 89 | # Get the current commit hash for the HTML file 90 | current_hash = get_current_commit_hash(html_file) 91 | if not current_hash: 92 | if args.verbose: 93 | print(f" Skipping {html_file} - not in a git repository") 94 | skipped_count += 1 95 | continue 96 | 97 | # Get the commit hash from the existing docs file (if it exists) 98 | existing_hash = extract_commit_hash_from_docs(docs_file) 99 | 100 | # Check if documentation needs to be updated 101 | if existing_hash == current_hash: 102 | if args.verbose: 103 | print(f" Documentation is up to date for {html_file}") 104 | skipped_count += 1 105 | continue 106 | 107 | if args.dry_run: 108 | print(f"Would generate documentation for {html_file}") 109 | updated_count += 1 110 | continue 111 | 112 | # Generate documentation 113 | if args.verbose: 114 | print(f" Generating documentation for {html_file}") 115 | 116 | doc_content = generate_documentation(html_file) 117 | if not doc_content: 118 | print(f" Failed to generate documentation for {html_file}") 119 | skipped_count += 1 120 | continue 121 | 122 | # Add the commit hash marker 123 | doc_content += f"\n\n" 124 | 125 | # Write the documentation to file 126 | with open(docs_file, "w", encoding="utf-8") as f: 127 | f.write(doc_content) 128 | 129 | if args.verbose: 130 | print(f" Documentation written to {docs_file}") 131 | 132 | updated_count += 1 133 | 134 | print( 135 | f"Documentation process complete: {updated_count} files updated, {skipped_count} files skipped." 136 | ) 137 | 138 | 139 | if __name__ == "__main__": 140 | main() 141 | -------------------------------------------------------------------------------- /writing-style.docs.md: -------------------------------------------------------------------------------- 1 | The Writing Style Analyzer helps improve your writing by identifying problems that weaken your prose. The tool automatically detects weasel words (vague terms that lack precision), passive voice constructions, and accidentally duplicated words. Paste your text into the input area to receive immediate feedback with highlighted issues and their surrounding context. 2 | 3 | -------------------------------------------------------------------------------- /yaml-explorer.docs.md: -------------------------------------------------------------------------------- 1 | YAML Explorer is a web-based tool that allows you to visualize and explore YAML data in an interactive tree view. You can paste YAML directly into the text area or load it from a URL, and the tool will convert it into a navigable structure with expandable sections. The explorer preserves your view state in the URL so you can share specific views with others. 2 | 3 | -------------------------------------------------------------------------------- /youtube-thumbnails.docs.md: -------------------------------------------------------------------------------- 1 | This tool enables you to view and download YouTube video thumbnails in various resolutions. Enter a YouTube URL or video ID in the input field to see all available thumbnail versions with their dimensions. Click on any thumbnail to expand it, or use the clipboard icon to copy the direct image URL for use in your projects. 2 | 3 | -------------------------------------------------------------------------------- /youtube-thumbnails.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | YouTube Thumbnail Viewer 7 | 59 | 60 | 61 | 62 |

YouTube Thumbnail Viewer

63 |

Enter a YouTube URL or Video ID:

64 | 65 | 66 |
67 | 68 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /zip-wheel-explorer.docs.md: -------------------------------------------------------------------------------- 1 | This tool allows you to explore the contents of package files directly in your browser. Enter a URL to a zip, tar.gz, or wheel file, and the browser will download and extract the contents, displaying a list of files that you can click to view. The tool supports Python wheel packages from PyPI and other package formats without requiring any installation. 2 | 3 | -------------------------------------------------------------------------------- /zip-wheel-explorer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Package File Browser 5 | 6 | 35 | 36 | 37 |

Package File Browser

38 | 39 | 40 |

Try a wheel from PyPI like: https://files.pythonhosted.org/packages/ae/8f/e0661dd3077b7343419ddb5ff840f6bcf571e8d11d8acb031e1b167de79c/llm_mistral-0.8-py3-none-any.whl (try it)

41 | 42 | 43 | 44 | 45 | 86 | 87 | 88 | --------------------------------------------------------------------------------