Skip to main content

Projects

·1099 words·6 mins·
Table of Contents

Most of what I have built belongs to somebody else. These are the ones that do not — small tools written to scratch my own itch, kept public in case the itch was not only mine.

They are all works in progress, and none of them are asking to be depended on. Have a look around anyway; issues and pull requests are welcome.

Repositories
#

org-roam-to-obsidian

Points at an org-roam vault and hands back notes Obsidian can open. ID links become wikilinks, org properties become YAML front matter, source blocks become fenced code, and the org-mode scaffolding is dropped on the way through. Standard library only, so there is nothing to install before you run it.
python3 convert.py --help
usage: convert.py [-h] [-i INPUT] [-o OUTPUT] [-p PROPERTIES [PROPERTIES ...]]
                  [--no-created] [--title-heading] [--keep-dead-links]
                  [--publish-when PROPERTY=VALUE] [--publish-key KEY]
                  [--created-key KEY [KEY ...]] [--modified-key KEY [KEY ...]]
                  [--no-modified] [--created-property CREATED_PROPERTY]

Convert org-roam files to Obsidian markdown format

options:
  -h, --help            show this help message and exit
  -i, --input INPUT     Input directory containing org-roam files (default:
                        ~/Documents/slip-box)
  -o, --output OUTPUT   Output directory for markdown files (default:
                        ./output)
  -p, --properties PROPERTIES [PROPERTIES ...]
                        Org-mode properties to extract (e.g., filetags
                        roam_refs)
  --no-created          Disable adding created timestamp from filename
  --title-heading       Repeat the title as an H1 at the top of the body. Off
                        by default, because Obsidian and Quartz already show
                        it
  --keep-dead-links     Keep wikilinks whose target does not exist, instead of
                        unwrapping them
  --publish-when PROPERTY=VALUE
                        Mark a note for publishing when it carries this org
                        property value, for example category=public
  --publish-key KEY     Frontmatter key for the publish flag (default:
                        publish)
  --created-key KEY [KEY ...]
                        One or more frontmatter keys for the created timestamp
                        (default: created). Quartz also reads created_at and
                        date
  --modified-key KEY [KEY ...]
                        One or more frontmatter keys for the modified
                        timestamp (default: modified). Quartz also reads
                        lastmod, updated and last-modified
  --no-modified         Disable adding the modified timestamp from the org
                        file's mtime
  --created-property CREATED_PROPERTY
                        Use a specific org-mode property for created timestamp
                        instead of filename

PythonGPL-3.0

gwttr

A small command-line weather client for wttr.in. The client is a proof of concept and stays that way on purpose — the point was somewhere to hang a real Go toolchain, so it comes with tests, coverage reporting and a published module.
./gwttr
Weather report: honolulu

      \   /     Clear
       .-.      26 °C
    ― (   ) ―    23 km/h
       `-’      10 km
      /   \     0.0 mm

GoGPL-3.0

Washy Washy

washy-washy-cli

Turns a CSV of laundry piles into two PDFs — one tall page for your phone at the machine, one A4 sheet to pin beside it. Every card is drawn for your appliances: you describe the washing machine and iron once in JSON, and each pile then shows the programme dial with the pointer where you turn it, the temperature and spin, which buttons to press, and the iron’s thermostat ring. Fascia labels are never translated — if the dial says Fijn/Zijde, so does the card.
Two washy-washy instruction cards side by side, each with a drawn programme dial, temperature and spin chips, and iron guidance
bun run generate --help
Usage: bun run generate [csv] [--out <dir>] [--machine <file>]

  csv               instruction CSV to read (default: data/washing-instructions.csv,
                    falling back to the committed .dist)
  --out <dir>       where the six PDFs go (default: out)
  --machine <file>  the appliances to draw (default: data/machine.json,
                    falling back to the committed .dist)

TypeScriptGPL-3.0

washy-washy-web

Live demo
The washy-washy laundry chart as a web page instead of a PDF — filter it by pile or by what’s due for a wash, download it, or share the filtered view as a link. Add it to your phone’s home screen so it opens by the machine with no address bar. Upload your own chart and machine file, or edit every field — piles, programmes, temperatures — right in the browser, all kept on your device rather than a server.
The washy-washy web app showing the bundled example laundry chart, with a filter panel for cutting it down by pile or by wash, and buttons to download the sheet as a PDF or share the current view The washing loads editor: upload or download a config, a summary of the current machine, and editable cards for every pile in the chart The machine editor: name, capacity, dial programmes in order and temperatures, all editable fields for describing your own washer and iron
  • Cloudflare Pages

AstroGPL-3.0

washy-washy-core

Chart parsing, machine validation and mixing rules for washy-washy, published on npm as @washy-washy/core — checks a CSV or JSON laundry chart against your washing machine and iron, and works out which piles can share a drum. One entry point for Node or Bun with CSV parsing, and a browser build that reads and writes charts as JSON instead.

TypeScriptGPL-3.0

form-handler

Turns a contact form submission into an email, which is the whole job. It exists because a static site cannot open an SMTP connection and the mail for these domains is self-hosted, so routing the form through a third party was the alternative. One instance serves any number of forms across any number of domains, each with its own allowed origins, so one site cannot post to another’s form.
form-handler --help
Accepts contact form submissions over HTTP and emails them on.

Configured from the environment, or from a forms file when FORMS_FILE
is set. See the README for the variables it reads.

Usage:
  form-handler [flags]

Flags:
      --healthcheck   probe the local /healthz and exit
  -h, --help          help for form-handler
  -v, --version       version for form-handler

GoGPL-3.0

backup-git-repos

Mirrors every repository on a self-hosted GitLab or Forgejo instance, or a GitHub.com account, to a local, restorable copy — a bare clone of every branch and tag, kept under the forge’s own namespace structure. It tells archived repositories from active ones, refreshes existing mirrors incrementally instead of re-cloning, and can write either set out as a .tar.gz alongside the mirror.
backup-git-repos --help
Back up git repositories from GitLab, Forgejo, and GitHub

Usage:
  backup-git-repos [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  config      Manage the backup-git-repos config file
  help        Help about any command
  list        Print what would be backed up, clone nothing
  run         Mirror repositories from the configured forges
  version     Print the version

Flags:
  -c, --config string   config file (default: $XDG_CONFIG_HOME/backup-git-repos/config.yaml)
  -h, --help            help for backup-git-repos

Use "backup-git-repos [command] --help" for more information about a command.

GoGPL-3.0