Building my own tools site (and the one dumb DNS gotcha)

Simon Willison is writing great coverage on LLM, and his little web toolbox at https://tools.simonwillison.net/ got me inspired — use Github Pages to make any file in a tools repository automatically available.

So of course I wanted one too: tools.codeandlife.com.

I created https://github.com/jokkebk/tools, enabled GitHub Pages for it, and set a custom domain tools.codeandlife.com in the repo settings. GitHub does the rest: deploy on push, serve static HTML. This should be plenty for one page HTML utils.

The DNS part (OpalStack edition)

The plan was the usual:

  1. Make a CNAME record for tools pointing to jokkebk.github.io
  2. Wait for DNS to propagate
  3. ...
  4. Profit!

But I got stuck in a surprisingly silly place: Opalstack allowed me to "Add DNS Record" for my codeandlife.com domain, but it only had one field and TTL (time-to-live) setting, not two:

OpalStack single field CNAME entry

I tried to enter "tools CNAME jokkebk.github.io" as well as "tools jokkebk.github.io" to it, but it did not work. Turns out the correct approach was against what ChatGPT instructed:

  1. Create the tools subdomain in OpalStack control panel first
  2. Then add the custom DNS record for it: CNAME jokkebk.github.io

Running dig tools.codeandlife.com confirmed all was working. Committing a test.html resulted in an error page, but after I tried a minute later, it worked! GitHub Pages has happily accepted tools.codeandlife.com as the custom domain, and only thing remaining is to actually create some tools there. :)

Note: Actually, looks like I needed also to wait for Github to DNS check my domain (took about 15 minutes) and then issue a certificate for it (another 5-10 minutes) so I got https://tools.codeandlife.com/ working. I checked "Enforce HTTPS" so now I should be good to go!

Enforce HTTPS in Github Pages setttings

Read post

JGoBoard Gets a Modern Makeover: Version 4.0.3 Released with Vite, Bug Fixes, and AI Help!

After about 6 years in hiatus, AI vibe coding finally enabled me to update my JavaScript-based Go board rendering library, jGoBoard. We've (meaning Claude Code and I) just rolled out version 4.0.3, and it brings a modern build stack and a bugfix to boot.

Also thanks to Gemini CLI which composed the structure of this post, although the signature AI fluff needed to be heavily edited afterwards.

For those unfamiliar, jGoBoard is a library designed to make rendering Go boards (gobans) in your web projects a breeze. It's been around for a while, but using it in modern build processes and ES module based environment was painful, and Grunt used for building has been in maintenance mode for years, not to speak about some of the other parts that were overhauled.

What's New and Exciting?

  • Switch to Vite in build tools
  • Can run "npm run dev" if you want hot reloading while tinkering with the code and demos
  • Outputs ES Modules, CommonJS, and UMD formats, plus better tree-shaking. This translates to smaller file sizes for your projects.
  • Removing superagent dependency that gets rid of security vulnerabilities in the outdated versions of the past

What's Slightly Less Exciting?

  • Fix for Github Issue #9: No more illegal move popups when accidentally clicking on coordinate labels outside the board area. The click handler now includes proper bounds checking and coordinate validation.
  • Improved Demo UX: The "Play demo captures" experience has been refined for a smoother interaction.
  • Demo Fixes: Even demoTree.html got some attention, with SGF loading fixed (though it's still a work in progress, as noted in the commit!).

And in a nod to the future (and perhaps a theme from some of my recent posts!), you might spot a familiar co-author in the commit history: Claude Code lent a hand in some of the changes, particularly with the version bump and dependency removal. It's always interesting to see how AI can assist in the development workflow!

Get Started

If you're looking to integrate a Go board into your web application, jGoBoard is easier than ever to get started with. Just head to https://jgoboard.com or simply clone the github repo https://github.com/jokkebk/jgoboard and check out the README.md there.

Read post

Vibe coding a food diary bot in one hour

I haven't written much about the new fancy AI tools in the blog, so I thought I'll do a short writeup on a sample workflow through ChatGPT, VS Code with GitHub Copilot and Claude Code that resulted in a fully working food diary app in about one hour from the idea.

The Problem Statement

I decided keeping a food diary would be nice way to avoid unhealthy habits. I experimented with using ChatGPT's vision capabilities some time ago to do calorie estimates, and that worked pretty well, so I thought I'd combine a simple Google Sheet and occasional ChatGPT use for a 95 % working solution without needing to bother with installing an app that would likely want to overhaul my whole diet, and subscribe me to a $19.99/mo plan while at it.

However, manually firing up the Google Sheet when on computer (the mobile version is crummy) wasn't ideal, so enter ChatGPT -- maybe I could do a native Android client with AI calorie estimates easily? So I asked GPT-5:

I'd like to create a super simple food diary app on my Android phone:

  1. Add a diary entry either by writing what I ate, taking a picture, or both
  2. Gemini API used for a calorie estimate from AI
  3. Diary stored permanently on my server (maybe including the pics?)

Outline for me three different architectures. I have a Raspberry Pi available, and a cloud server. I'd like minimal backend solution for storage which I can also view manually (say JSON or SQLite or even Google Sheet). Native app would be fun, but at least something I can quickly fire up from phone and upload photo.

After a while of thought, GPT5 kindly gave me three ideas, to summarize:

  • Telegram bot + tiny API on your Pi/cloud (fastest to use from phone)
  • Google Sheets + Apps Script Web App (no server to run)
  • Native Android app (Kotlin) + tiny API on Pi/cloud (most control, best UX)

Now the first one was actually pretty smart. I had recently done a Telegram bot for a IoT project and it was quite easy, and it would readily come with picture sending and chat capabilities. That would be plenty.

Initial version: The Spec

Continuing with the prompt, I gave a sketch of how the interaction could go:

I think the telegram bot sounds nice. I'd like to be able to group messages within a short period to impact the same entry, so the interaction with bot could be like:

> Had a pasta carbonara for lunch

< That's about 700 calories

> [image]

< Based on your description and photo, that's about 900 calories

> 850 cal

< Thanks, adjusted based on your input.

And on the AI went. I got these artifacts out:

  1. FastAPI server (SQLite + media + Gemini)

Read post

Turn Comments into Fish Shell Commands with OpenAI

***Update 2025: I've had more trouble with this fish binding than it is worth. I've crippled my fish shell irrecoverably a couple times and had numerous issues trying to adapt for bash and zsh. I am keeping this post for reference, but I would strongly advice using a proper agentic tool nowadays with some guardrails. ***

Read on if you want to read about my journey to make fish transform comment lines to runnable commands.

Read post

How to upgrade ollama docker image without losing models

After a bit of AI hiatus, I noticed that llama 3.0 models were released and wanted to try the models. Sure enough, after a week the weights we re available at the official site. However, the Docker image hasn't been used in a while and I wanted to upgrade it without losing the models.

There was almost no information on this available online yet, and even the ollama docker documentation is quite non-existent — maybe for seasoned Docker users it is obvious what needs to be done? But not for me, so let's see if I can manage it.

Upgrading the docker image

First, let's just upgrade the ollama/ollama image:

$ sudo docker pull ollama/ollama

This is nice, but the currently running container is still the old one. Let's stop it:

$ sudo docker stop ollama

Checking the location of the files

I remember I set a custom directory to store the models. Let's check where it is:

$ sudo docker inspect ollama | grep -i volume
            "VolumeDriver": "",
            "VolumesFrom": null,
                "Type": "volume",
                "Source": "/mnt/scratch/docker/volumes/ollama/_data",
            "Volumes": null,

As can be seen, the models are stored in /mnt/scratch/docker/volumes/ollama/_data. Let's make a hard-linked copy of the files into another folder, to make sure we don't lose them:

$ sudo bash
$ cd /mnt/scratch
$ cp -al docker/volumes/ollama/_data ollama_backup

Now that we have the models backed up, let's remove the old container:

Read post

Linux Shell AI made easy with ChatGPT automation

Continuing the awesome and not so unique stream of ideas on what to do with ChatGPT, here's a bit modified take to my previous post on self-running ChatGPT generated Python code. This time, let's do a shell script that takes a description of what you want as a shell command, and returns just that command. Here's how it will work:

$ shai find latest 3 files
46 total tokens
ls -lt | head -n 3
$ ls -lt | head -n 3

total 1233
-rwxrwxrwx 1 root root  5505 huhti   4  2023 python-chatgpt-ai.md
-rwxrwxrwx 1 root root 10416 maalis 26  2023 golang-sveltekit-chatgpt.md

As seen, this time I'm not running the command automatically, but just returning the command. This is a bit safer, as you can inspect the command before running it. The script is quite simple:

#!/usr/bin/env python

import sys, os
from openai import OpenAI
from configparser import ConfigParser

# Read the config file openai.ini from same directory as this script
path = os.path.dirname(os.path.realpath(__file__))
config = ConfigParser()
config.read(path + '/openai.ini')

client = OpenAI(api_key=config['openai']['api_key'])
prompt = ' '.join(sys.argv[1:])
role = ('You are Ubuntu linux shell helper. Given a question, '
        'answer with just a shell command, nothing else.')

chat_completion = client.chat.completions.create(
    messages=[ { "role": "system", "content": role },
              { "role": "user", "content": prompt } ],
    model = config['shai']['model']
)

print(chat_completion.usage.total_tokens, 'tokens:')
print(chat_completion.choices[0].message.content)

I decided GPT 3.5 Turbo is a good model for this, as it should be able to handle shell commands quite well. You also need to have a openai.ini file in the same directory as the script, with the following content:

[openai]
api_key = sk-xxx

[shai]
model = gpt-3.5-turbo

To use it, just install the OpenAI Python package with pip install openai, and then you can use the script like this:

$ chmod +x shai
$ ./shai find latest 3 files

And putting the script in your path, you can use it like any other shell command. Enjoy!

Read post