← All tools tools.jamesking.io

tools.jamesking.io

A collection of small, single-purpose tools. Most are standalone, browser-based web apps; some are skills for an LLM or Apple Shortcuts. The landing page groups them into those three sections. Deployed at tools.jamesking.io.

The guiding principles:

The only build step in the repo is build.js, which generates the landing page, the styled README pages, and each tool's changelog and the site's RSS feed from git history. Individual tools never need compiling or bundling.

Repository structure

tools/
├── index.html              # GENERATED landing page (do not edit by hand)
├── feed.xml                # GENERATED RSS feed of new and updated tools
├── changelog-overrides.json # Reworded or hidden commit messages for changelogs
├── build.js                # Generates the index, README pages, changelogs and feed
├── README.md               # This file (rendered to /readme on the site)
├── netlify.toml            # Deploy config — runs `npm run build`
├── design-system/          # Shared tokens + components (see below)
│   ├── tokens.css
│   ├── components.css
│   └── index.html          # styleguide gallery
├── netlify/
│   └── edge-functions/     # e.g. rss-proxy.ts (CORS proxy for feeds)
└── <tool-name>/            # One folder per tool
    ├── index.html          # The tool (for hosted-here tools)
    ├── README.md           # Tool docs (first heading + paragraph feed the index)
    ├── readme.html         # GENERATED from README.md
    └── changelog.html      # GENERATED from the folder's git history

Adding a new tool

  1. Create a folder in the repo root named in kebab-case (e.g. pdf-merger/).

  2. Add index.html — your tool. Link the shared design system in the <head> (note the ../ — tools sit one level below the root):

    <link rel="stylesheet" href="../design-system/tokens.css">
    <link rel="stylesheet" href="../design-system/components.css">
    

    Then build the UI from the design-system classes (btn, card, field, badge, dialog, etc.). Keep only genuinely tool-specific layout in a small inline <style>, and reference tokens via hsl(var(--token)) so it tracks the theme. See syndicate-elsewhere/ for a worked example.

    Wrap the page in a .container, choosing its width by what the tool is for: container-narrow (768px) for guides and single forms, the default (1100px), or container-wide (1600px) for workspaces with sidebars or side-by-side panes. Don't set a page width in the tool itself. See Page layout.

    Start the page with the shared header and end it with the shared footer, so every tool links to its generated Readme and Changelog pages:

    <div class="container">
    <header class="tool-header">
      <a class="btn btn-outline btn-sm" href="/">← All tools</a>
      <span class="tool-header-title">PDF Merger</span>
      <div class="tool-header-actions">
        <a class="btn btn-outline btn-sm" href="readme.html">Readme</a>
        <a class="btn btn-outline btn-sm" href="changelog.html">Changelog</a>
      </div>
    </header>
    …
    <footer class="site-footer">
      <a href="https://jamesking.io">jamesking.io</a>
    </footer>
    </div>
    
  3. Add README.md — must start with a level-1 heading and a short summary paragraph. These are extracted for the tool's card on the landing page:

    # PDF Merger
    
    Combine multiple PDFs into one, entirely in your browser.
    

    The full README is also rendered as a styled page at <tool>/readme.html.

  4. Commit, then rebuild so the landing page, README pages, changelog and feed pick up the new tool:

    npm run build
    

    The changelog and feed come from git history, so a tool gets its changelog page, sparkline and "New tool" feed item only once its folder has been committed.

Externally-hosted tools

If a tool lives elsewhere (another domain, an Apple Shortcut, etc.), it still gets a card — create a folder with just a README.md and add YAML frontmatter:

---
external: true
url: https://fonts.jamesking.io
---

# Font collection

A short description for the card.

The "Open Tool" button will link to url (opening in a new tab) instead of a local index.html.

Skill-based tools

Some jobs can't run in a browser (e.g. downloading from YouTube). These can be packaged as a Claude Code skill that runs locally: put the skill in <tool>/skill/ (a SKILL.md plus self-contained uv scripts) and make index.html a setup guide. Secrets belong in the user's secret store (Keychain or 1Password), never in the repo. See glanceable-podcast/.

Say plainly where a skill runs. Skills also exist for Chat on claude.ai and for the API, where they run in Anthropic's cloud sandbox; a skill that needs the user's own machine (local network, secret store, installed tools) works only in Claude Code, and its setup guide should say so.

Landing-page sections

Each tool appears under one of three headings on the landing page, set with type in its README frontmatter:

type Section For
web (default) Web apps Tools that run entirely in the browser, hosted here or externally (e.g. the fonts collection)
skill Skills Tools bundled as a skill for an LLM (see below)
shortcut Shortcuts Apple Shortcuts
---
type: shortcut
external: true
url: https://www.icloud.com/shortcuts/…
---

Archiving a tool

When a tool is no longer needed, archive it rather than deleting it: add archived (the date) and archiveNote (why, in Markdown) to its README frontmatter.

---
archived: 2026-10-18
archiveNote: Observable now does this itself, so this bookmarklet is no longer needed.
---

The tool moves to an Archive section at the bottom of the landing page, listed by name with an Archived badge rather than as a card. It keeps its URLs, its page headers get an Archived badge, and the note is shown at the top of its Readme and under the archive day in its changelog. Its changelog marks that day Archived, and the feed gets an Archived item (ID tool:<folder>:archived) in place of that day's update. Add the badge and note to the tool's own index.html by hand. If the commit that archives the tool just restates the note, hide it with changelog-overrides.json.

Folders that are not tools

Shared infrastructure is excluded from the landing page via IGNORE_DIRS in build.js (currently design-system and netlify). Add to that set if you introduce more non-tool folders.

Changelogs and the RSS feed

Each tool's changelog, the activity sparkline on its card, and the site's RSS feed (/feed.xml) are all generated by build.js from the repo's git history. Nothing is written by hand. Commit messages are what readers see, so write them as short, plain descriptions of what changed for someone using the tool.

How commits are turned into entries:

What's generated from that:

Development conventions

These are also recorded in CLAUDE.md for AI-assisted work.

Design system

A shared, no-build CSS layer keeps the tools visually consistent. It lives in design-system/:

File Purpose
tokens.css Design tokens as CSS custom properties — colors (HSL channel triplets), spacing, page widths, type, radii, shadows. Light / dark / system theming.
components.css Class-based components: container (narrow / default / wide / full), btn, card, input/field, select, switch, badge, tabs, dialog, toast, table, prose, plus utilities.
index.html Living gallery of every token and component, with a light/dark toggle. Open this as the reference.

Theming is driven by data-theme on <html>:

Tools with a theme setting should resolve it in JS and set data-theme to light/dark.

Conventions: don't redefine component styles inside a tool — add only app-specific layout, and prefer a tool-scoped class over overriding a .btn/.card rule. When you add a new shared component, also add it to index.html. Full details in design-system/README.md.

Build & deploy

npm install        # one-time: install build/dev dependencies
npm run build      # generate index.html, README pages, changelogs and feed.xml
npm start          # serve locally (http-server) and open the index

build.js produces, from the project's README files and git history:

All of these are generated artifacts and are gitignored (/index.html, /readme/, */readme.html, */changelog.html, /feed.xml) — never edit them by hand; edit the source README.md / build.js and rebuild.

Deployment: Netlify runs npm run build on deploy (see netlify.toml) and publishes the repo root, so the generated pages are always fresh. The changelogs and feed need the full git history: if the deploy checks out a shallow clone, build.js fetches the rest first, and if history still can't be read it leaves out the changelogs, sparklines and feed rather than failing the build. Edge functions in netlify/edge-functions/ are deployed alongside — e.g. rss-proxy.ts, a CORS proxy used by syndicate-elsewhere to fetch RSS feeds from the browser, and feed.ts, which serves a radio-archive-rss podcast feed encoded into its own URL.