atomicules

Mostly walking the dogs

Where am I running AI in Tmux?

Since using lots of Vim sessions in a terminal I had a brief dalliance with Nova (I did/do like it, but it’s not cross-platform) and then ended up with Helix. At work I tend to use Helix in the same way I was using Vim but instead of Terminal.app it’s Alacritty with one huuuuge Tmux session with multiple windows (“tabs”) per each Git repository I’m working on. I haven’t bothered figuring out renaming the tmux windows automatically and just rename them manually - it tends to be a very long running Tmux session that I also save and restore so renaming manually is a one-time thing. It is easy to find whichever repo I want (across 70+ windows) by using CTRL, b, w to bring up Tmux’s window search and then using / to search for the name I want.

Since starting to play about with AI clis, I’ll split the Tmux window so I have a pane for running the cli in and a pane for Helix. But I won’t be running an AI cli in all of these windows which has lead me to wonder, “How do I find where I’m running AI clis?”.

What I’ve done is add a simple wrapper to the AI cli command to set and unset Tmux window titles on launch and quit of the AI cli:

function ai {
	# Let me know where I'm running an AI cli session
	
	# Find tmux window this command was called from
	# This way can target window. 
	# And avoid racey style stuff where wrong "current window" gets renamed
	twin=$(tmux list-panes -a | grep "$TMUX_PANE " | cut -d : -f 2 | cut -d . -f 1)
	twse=$(tmux list-panes -a | grep "$TMUX_PANE " | cut -d : -f 1)
	# I don't think I'd ever use an asterisk as a window name, but this would fallover if I did
	twnm=$(tmux list-windows -a | grep "$twse:$twin:" | cut -d : -f 3 | cut -d ' ' -f 2 | cut -d '*' -f 1)
	tmux rename-window -t $twin "$twnm + ai"
	/path/to/ai/cli
	tmux rename-window -t $twin $twnm
}

And now I can easily search through my Tmux windows.

LINK: Using Gemini Cli For Software Development

Meant to link to this at the time, but I meant to write my own blog post (see yesterday) before that, but it’s taken me until now to do either; AI certainly isn’t freeing up any spare time in my life.

Playing About With Ai Clis On Netbsd

NetBSD themselves have a no AI policy (fair; at some point lawyers are going to make a mint), but I just wanted to have a play about and since I run NetBSD, perhaps for too long, I just assumed I was out of luck with most AI tooling (most things do just target macOS, Windows and Linux). But it turns out things are mostly ok thanks to all the CLI tooling being node/npm:

  • Gemini just works* (and requires nothing more than a Google account; Which I have again since switching to Android)
  • Codex throws an unsupported platform (I wonder if this could be worked around?)
  • Claude runs (but there is no free tier)
  • Qwen code works (requires a free account; Looks like a Gemini clone)

(Don’t use global installs)

I started with Gemini - I thought I’d jump in the deep end and ask it to do something useful and something fairly complex so I asked it to build me a Chrome Cast receiver in python - I’d love to be able to cast from my Pixel to my NetBSD laptop. It “worked” in that it built something that looked sensible and did run although didn’t actually work when it came to being a Chrome Cast receiver. So then I thought I’d step back a bit and ask it to migrate simplenote.py (long neglected by me) from Travis to Github Actions; Because this is the kind of thing I’ve been meaning to do for a few years and just haven’t found the time for; I.e. this seems like the kind of “chore” task we should be using AI for instead of all the terrible things we are using AI for.

Comparing Gemini with Qwen

For both I gave a prompt like this (apart from the branch name being different):

Create a branch of this repository called gemini-github-actions and migrate the Travis CI setup to Github Actions

And then also gave both permissions for whatever it asked for for the session.

Both gave me additional instructions. Gemini:

To verify the migration, you should push this branch and ensure the GitHub Actions runner can successfully execute the tests and build the package. If you want to proceed with a commit, please let me know.

Qwen:

Next steps:

  1. Add PyPI credentials to GitHub repository secrets (PYPI_USERNAME and PYPI_PASSWORD)
  2. The deploy job triggers on release publication (modern approach vs. Travis’s tag-based deployment)

Personally I find it dumb that neither of them committed automatically (why would I be telling you to create a branch?) so I told both:

Please commit your changes including the summary of the changes you’ve just provided in the commit message.

Qwen had just created the .github/workflows/ci.yml file whereas Gemini also removed the .travis.yml and updated the README badges (nice); However, I LOVE that Qwen co-authored the commit by default.

I told Qwen:

Please also delete the .travis.yml and update the README badges to suit Github Actions ​

to get it in the same state.

(Interesting aside… I had to update my PAT to allow creation and updates of workflows before I could push)

Here’s the final results (final at the time of publishing is one commit on the gemini branch and two on the qwen one):

  1. Gemini
  2. Qwen

There are some subtle differences. Gemini includes many more python versions, Qwen just two, matching exactly what I had for Travis.

Of these two, only the Gemini branch automatically triggered a Github Actions run because it sensibly included its own branch - it failed though on every single job. Don’t actually know why. Github just tells me all the jobs exceeded the maximum execution times whilst waiting for runners.

In an effort to actually publish this post this year I am going to leave the AI efforts there for now - I might continue with both branches and get Gemini and Qwen to make further tweaks or I might continue by hand, but that could take me days, or weeks. Or months. So this will do for now. There’s no conclusions to this post, just observations - literally just me documenting my playing about.

This post has taken me months to write. I’m just too busy. I’ve been wanting to play with AI CLI tooling because up until just the last day or so I’ve not been allowed to use any such tooling at work (believe it or not). At least my tinkering on NetBSD did give me a little bit of a headstart and familiarisation.


* - Worked until this change since that it now gives env: unknown option -- S on NetBSD. Reverting that makes it work again. For me that was these files:

  • /home/me/node_modules/.bin/gemini
  • /home/me/node_modules/@google/gemini-cli/dist/index.d.ts

Making Use Of Having Netbsd Hardware Again

It took me a bit to get up and running after getting the laptop, but did want to point out that I am actually making use of it now:

  • I make use of the excellent pdh on my NetBSD server so it means I can somewhat have an unofficial PagerDuty client on my phone (for “reasons” I can’t use the official one). But it bugged me that plain output was broken and that I couldn’t snooze alerts. So I fixed them both; Technically I didn’t need local NetBSD hardware for this since I only run pdh on my server, but just having a portable computer which is mine doesn’t half make it easier to do this.
    • I spent quite a bit of time looking at pre-commit and ruff-pre-commit as part of this. In the end I’ve not pushed anything, but I patched pre-commit locally so it works after I install pysqlite3, but then still ran into issues building ruff-pre-commit on NetBSD and I couldn’t find a work-around even though I could install ruff itself fine.
      diff --git a/pre_commit/store.py b/pre_commit/store.py
      index 1235942..ba2469a 100644
      --- a/pre_commit/store.py
      +++ b/pre_commit/store.py
      @@ -3,7 +3,11 @@ from __future__ import annotations
       import contextlib
       import logging
       import os.path
      -import sqlite3
      +try:
      +    # Primarily for BSDs that don't have built-in
      +    import pysqlite3
      +except ImportError:
      +    import sqlite3
       import tempfile
       from collections.abc import Generator
       from collections.abc import Sequence
    
  • One of the first things I did on this machine was build helix. It built fine outside of Pkgsrc, but I wanted to update the Pkgsrc package and having local NetBSD hardware meant I could finally do it. Now I just need someone to merge it.
  • Similarly with spotifyd, especially since I’m the supposed maintainer of the Pkgsrc package. That really can only be tested on local hardware.
  • And libvips too.
  • I had some thoughts about trying to setup my own taskwarrior sync server and put together a Pkgsrc WIP package for taskchampion-sync-server, but I don’t think I’m going to pursue this as using the mobile app requires running CCsync as well which just seems silly. Maybe I’ll re-visit this at some point. Maybe I’ll go back to TeuxDeux.

Not Computerless

Not Computerless

Finally! Took me just over three years to save up for a old Thinkpad X270 (~£85). It seems to be in excellent condition and interestingly, despite being a bit more chunky than my work’s Macbook Pro M1 14”, it feels a lot lighter. And arguably has a better keyboard. Also, it might be a seven year old machine, but for a progression in NetBSD hardware for me that has gone from Pentium III, to Pentium D this thing feels like the future!

My plan was that I’d get a X270 or T470 (but not anything newer) as that way I could put the SSD I’d pulled from my old Optiplex into it and immediately have a working NetBSD machine that I could then upgrade. But after three years that SSD was unlikely to work and things had moved on a fair bit. So I opted for a fresh NetBSD 10.1 install (after a final boot into the Windows OS it came with to update the BIOS). I mostly followed this approach (encrypted swap comes free with NetBSD 10), but with these changes (these notes are more for me than anything):

gpt add -l efi -t efi -s 128m ld0
gpt add -l netbsd -t ffs -s 20g ld0
gpt add -l swap -t swap -s 8g ld0
gpt add -l netcgd -t cgd ld0

On my NetBSD server I have 5Gi /root and 10Gi /usr. Here I want /usr unencrypted (because I just do), so I’ve opted for 20Gi so I have a bit of breathing room. For cgd I have:

/dev/cgd0b  /tmp  mfs     rw,-s=132m    0 0
/dev/cgd0e  /var  ffs     rw            1 2
/dev/cgd0f  /home ffs     rw            1 2

(I set a to unused).

I have 2Gi for /tmp and 10Gi for /var. I kind of wish I’d done a bigger /tmp directory as I’ve already maxed it out with some cargo builds (but hopefully only because it didn’t clean up some previous attempts); Have to be honest that I don’t fully understand the /tmp changes with NetBSD 10. I seem to have both a /tmp and a /var/shm tmpfs - I don’t know if that’s intentional or I’ve just done something wrong. Maybe some day I could change this, but probably not any time soon.

I used to use dwm and st, but since I was setting up from scratch I decided to give leftwm a whirl to go with alacritty which I’ve been using on macOS for the past few years. I do like that dwm is “self-contained”, whereas with leftm you have to worry about status bars, etc. I’ve opted for lemonbar after making a small sed change (sed -rg '/^\s*$/d') in the script for NetBSD. I’m showing battery usage based on this approach (I have two batteries though!) and built this little status script:

#!/bin/sh
# file:~/bin/leftwmstatus
while true; do
        load=$(cat /proc/loadavg | awk '{print $1, $2, $3}')
        # Memory Free / Swap Free
        mem=$(cat /proc/meminfo | awk 'FNR==5{printf ("%.1f/", $2/1024/1024)};FNR==10{printf ("%.1f GB", $2/1024/1024)}')
        cal=$(date +"%a %b %d %Y %H:%M")
        batt1=$(envstat -s acpibat0:charge | grep charge | sed 's/.*(\(.*\)%)/\1/')
        batt2=$(envstat -s acpibat1:charge | grep charge | sed 's/.*(\(.*\)%)/\1/')
        charge=$(if envstat | grep -Eq 'connected.*TRUE'; then echo ^; fi)

        printf "S$batt1%%+$batt2%% $charge | $load | $mem | $cal\n"
        sleep 2
done

A few random “issues” I’ve had:

  • Needing to set XDG_RUNTIME_DIR for leftwm
  • I’ve not been able to enable XDM yet because I hit keyboard input conflict issues.
  • An occasional fsck issue on boot I have no clue about because it’s moaning about fsck_home being missing (which doesn’t seem to be a thing).
  • I had TLS/SSL errors doing the initial install with pkg_add. I can’t actually remember how I resolved this. I think I first of all had to set pkg_path to use http to get the Mozilla root certs and then I could switch it to https.
  • Suspend doesn’t work. At home this isn’t a huge problem as I just leave it turned on.
  • I have somewhat got the /etc/powerd/scripts/acadapter script working to lower the frequency when on battery (sometimes it doesn’t trigger, I don’t know why; Not a massive deal).

At the time I set this up the Pkgsrc version of Helix was set as broken, but building it outside of Pkgsrc was easy; I am going to see if I can get Pkgsrc Helix updated now I actually have NetBSD hardware to do that on.

I’ve got it setup enough that I can use it, but still need to tweak a few things:

  1. Finding a good way to type £ (it’s an American keyboard).
  2. Disabling taps on the track pad because I keep clicking accidentally.

I’m very happy to have NetBSD hardware again!

LINK: And Repeat

I am making a point of taking my whole 1.5% allowance this year, but my months go something like this:

  1. January: Save money for…
  2. February: Mum’s birthday, Dad’s birthday and Valentine’s day.
  3. March: Mother’s day and paying some of February off
  4. April: Catching a break (but typically some cheap/second hand running shoes at this point) and also probably paying for some of February.
  5. May: A’s birthday
  6. June: Father’s day
  7. July: ?
  8. August: ?
  9. September: ? (Maybe saving money for a Christmas present for A).
  10. October: Probably another pair of running shoes
  11. November: Saving money for…
  12. December: Christmas presents for Mum and Dad.

So I’ve two to three months where I could save. I’m trying to be more aware (writing this out has helped me) and (even more) realistic this year: Stuff for A shouldn’t be included on this budget, but seemingly is; Technically one pair of running shoes would do for the total distance I run, but they also get used for dog walking; Second hand running shoes from Vinted help save cash; April and October give me six months per pair of shoes and also kind of works seasonally.

If nothing else crops up in July and August then I shouldn’t have to repeat this post again next year.

Taskwarrior 3 on NetBSD Notes

If I have time (“ha ha ha ha”; I’m also doing this all via ssh from my phone) I’d like to see about making a Pkgsrc WIP package for this (since it’s quite a change from v2 it makes more sense to stick it in WIP rather than just upgrade the existing Pkgsrc package). On that note, I wonder if it’ll have to be split out into taskchampion and taskwarrior for Pkgsrc? I.e. so the new Rust taskchampion bit is built first and then the C++ taskwarrior bit includes that?

In the meantime I just built outside of Pkgsrc:

  1. Needed a small patch to build:

     diff --git a/CMakeLists.txt b/CMakeLists.txt
     index 25ed4d9eb..a644268ac 100644
     --- a/CMakeLists.txt
     +++ b/CMakeLists.txt
     @@ -65,7 +65,11 @@ SET (TASK_RCDIR "${TASK_DOCDIR}/rc" CACHE STRING "Installation directory for con
      SET (TASK_BINDIR  bin            CACHE STRING "Installation directory for the binary")
         
      # rust libs require these
     -set (TASK_LIBRARIES dl pthread)
     +if (NETBSD)
     +  set (TASK_LIBRARIES pthread)
     +else ()
     +  set (TASK_LIBRARIES dl pthread)
     +endif ()
         
      check_function_exists (timegm  HAVE_TIMEGM)
      check_function_exists (get_current_dir_name HAVE_GET_CURRENT_DIR_NAME)
    
  2. Needed this exporting: CARGO_HTTP_CAINFO=/usr/pkg/share/mozilla-rootcerts/cacert.pem
  3. And then a cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=ON . to keep libuuid linked for the install; I am wondering if libuuid is even needed on NetBSD, probably not, but that can be an exercise for another day.
  4. The rest of it is per the INSTALL instructions.

I also updated my patch from ten years ago to fix recurring tasks for DST for v3:

diff --git a/src/recur.cpp b/src/recur.cpp
index d6bb47454..b11480245 100644
--- a/src/recur.cpp
+++ b/src/recur.cpp
@@ -53,6 +53,8 @@
 
 // Add a `time_t` delta to a Datetime, checking for and returning nullopt on integer overflow.
 std::optional<Datetime> checked_add_datetime(Datetime& base, time_t delta) {
+  Datetime recurrence_date;
+
   // Datetime::operator+ takes an integer delta, so check that range
   if (static_cast<time_t>(std::numeric_limits<int>::max()) < delta) {
     return std::nullopt;
@@ -62,6 +64,14 @@ std::optional<Datetime> checked_add_datetime(Datetime& base, time_t delta) {
   if (std::numeric_limits<time_t>::max() - base.toEpoch() < delta) {
     return std::nullopt;
   }
+  // Shift current to midday, to be safe of any DST changes, before calculating future date
+  // Then set back to correct hour
+  // Only do this if delta is a whole number of days
+  if (delta % 86400 == 0) {
+    recurrence_date = (Datetime (base.year(), base.month(), base.day()) + 43200) + delta;
+    return Datetime (recurrence_date.year(), recurrence_date.month(), recurrence_date.day()) + 3600 * base.hour() + 60 * base.minute() + base.second();
+  }
   return base + delta;
 }

It seems there maybe future plans to use timezones in tasks which would finally solve this properly so I guess since my approach is still a little hacky it’s not worth PR’ing (the idea didn’t get any traction in the last ten years anyway).

LINK: Code The City

Despite being in Aberdeen for seventeen years this is the first I’ve heard of Code The City! I could have (in theory) done all of these although I suppose only realistically since 2022 after moving from the Shire to the City; However, my initial excitement was rapidly dampened when I remembered I still don’t have a laptop - in fact it’s been since about 2016 when I last had a (Pentium III) laptop. And there is also no way I’d actually be allowed to spend all weekend doing something like this. Still, it’s neat that this event exists at all.

Maybe one day.

Micro Desking

Micro Desking

Look at that iMac. So close yet so far.

Had to do some recent further downsizing to accommodate squeezing another person into the house. My desk (which was off to the left of this photo) had to go in the garage to make way for a fridge freezer so I’m now micro-desking on the end of the cabinet storing the dogs’ stuff. I’m (obviously) still in the utility room/corridor with the dogs, washing machine (yay for noise cancelling in meetings) and shower room, but now I’m sat on top of the cat litter instead of half a metre away from it. It doesn’t sound great, but it is actually a nice little neuk.

Before the move was complete I did have an intermediate week when I ended up with the family computer on my desk where I could easily use it whilst “at work” and this meant I did finally get to explore using CSS for the show/hide on my archive page instead of JavaScript. Nothing came of it (I don’t think CSS can do that yet), but it was nice to get to tinker again - like with exercise, it’s nice to know it’s not lack of motivation on my part that is the problem.

I decided to put away my old iPhone and plug my headphones into the iMac instead. Since it is always logged in as me anyway (for AirMessage) I can also leave Spotify running and just control it through my phone. For 6music I ssh (hit exactly this issue) from my phone and use tmux and ffplay. This is cool and fun, but not really any more useful than having my old iPhone plugged in, although does save a tiny bit of space - and space… is short.

For the weekly finance updates I close my work laptop so I can stick the iMac keyboard on top of it, the trackpad in front of it and then swivel the iMac round. It works well enough for occasional use.


[EDIT: 2024-09-05] A little while ago it was decided I was marginally more important than the dogs so we got rid of their green storage cabinet and bought my desk back in. Which means I also have the iMac on my desk! Whoop! Sometimes cats and cat food too, but ach. I think micro-desking could have been doing my back in. Forgot to mention originally, but that strip of floorspace is my yoga/workout space, which is not quite the space needed to rollout a yoga mat, but I’m making it work.

[EDIT: 2024-10-31] And then the younger daughter went to Uni so it was decided that my desk would be moved into her room. So far so good. But then my chair got replaced with a pretty one “to suit the room” that is unfortunately too low for a desk. And then my desk got replaced with a dressing table that is too shallow and too wobbly to use as a desk. On the plus side, when my daughter is back from Uni I will be allowed to work at the dining table; Can’t day to day as that results in “too much clutter”; I also can’t go back to micro desking as that “desk” now has an air-fryer and microwave on it; What does work is putting the laptop in my lap when working and just using the dressing table as somewhere to store/charge my laptop; I can also put the iMac keyboard on my lap to type - just like now.

[EDIT: 2024-12-09] And back to micro-desking. Although now looking over my work laptop I get to see a microwave and air fryer stacked on top - turns out there was room. Just.

[EDIT: 2025-02-04] Still micro-desking for the foreseeable future, but forgot to mention previously that my plan to work at the dining table was scuppered when A unnecessarily replaced the rectangular dining table with a round one - and a round table with a centrepiece means there isn’t enough room to sit with a laptop on the table.

[EDIT: 2025-06-05] Just under a year later and the elder daughter landed a big girl job in London! This meant that after three years of moving into this house I finally got to use the built-in office as my office. Whoop!

LINK: Repeat

The least surprised person is me.

Started as a much longer post, but, you know what? I’ve said it all before.

These are the ten most recent posts, for older posts see the Archive.