diff --git a/src/blog/outsourcing-my-memory-to-gum/index.md b/src/blog/outsourcing-my-memory-to-gum/index.md
new file mode 100644
index 0000000..d031fe1
--- /dev/null
+++ b/src/blog/outsourcing-my-memory-to-gum/index.md
@@ -0,0 +1,128 @@
+---
+title: Outsourcing My Memory to Gum
+layout: article.njk
+date: git Last Modified
+tags: post
+excerpt: For years, I've been peeking at package.json to see how to use projects I've been away from. Now, I've built a better method that's held together with gumโฆ
+---
+
+My memory is notoriously bad. I forgot what I ate today or whether I washed my hair. I walk into rooms and forget why. I tell friends stories they were themselves involved in.
+
+You better believe that, when I return to some sort of web development project after several months, I've long since forgotten how to do anything with it. This is compounded by the fact that every tech stack has their own default way of doing things. For example, some npm project bootstrappers will create a script called `start` while others will create one called `develop`, both of which are used to start a local development server. I will sometimes add scripts for various types of deployment, which are different depending on the project, the tech stack it's on, where it's deployed, and how good I am at naming things on that day. ๐
+
+## My Crappy Workaround
+
+I usually work around these problems on Node-based projects by starting with `npm run` when I return to a project to get an overview of the project's scripts so I can understand how to jump in.
+
+
+
+(Confession time: in truth, I `cat package.json`, but `npm run` is what I _should_ have been doing instead.)
+
+This is cumbersome and clunky though. I'm manually looking at a file or at some output based on a file to figure out what options I have. Then, I manually run the option that does what I'm trying to do. The data that I'm looking at is already nicely structured in JSON, so it seems like a natural fit for the computer to handle the first part for me and just present me with options I can run directly. Now, let's see how to make that happenโฆ
+
+## A Cleaner Approach with Gum
+
+That's when I stumbled upon [Charm's](https://charm.sh/blog/) [Gum](https://github.com/charmbracelet/gum). Gum is basically a UI component library for shell scripts. Gum has [a `choose` command](https://github.com/charmbracelet/gum?tab=readme-ov-file#choose) that outputs a handy option chooser component. This would be a great UI for my use case. I will need to pipe the available options into it and then run the result. Let's start putting the pieces together.
+
+I would like a single command I can run from any npm project. To accomplish this, I'll write a function into my shell's config. In my case, that's `~/.zshrc`. I call the function `donpm` which you can interpret as either "do npm" or "Don P.M." who I imagine as a cool person with aviator shades who only comes out at night (yes, even with the shades; they're _that_ cool) and can figure out exactly how all your npm projects work. ๐
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ $SELECTION=(jq -r '.scripts|to_entries[]|((.key))' package.json | gum choose)
+ npm run $SELECTION
+}
+
+# loads more stuff
+```
+
+This function has a dependency on `jq`. I could probably do this with `sed`, but it's easier with `jq` and this is just for me to run on my system so I'm going to use it. With `jq`, I'm reading the contents of `package.json` and parsing out the scripts. I pipe that over to `gum choose` to display the chooser component.
+
+`gum` will spit back the selection. I need to capture that so I can run the selected command, hence assigning the result to a variable (`SELECTION`).
+
+Once I have that captured, I can then run `npm run $SELECTION` to interpolate the selected command into my `npm run`. (I tried piping to this, but that didn't seem to work. Maybe there's a fancier, better way, but this one seems to work well enoughโฆ well, almost. ๐)
+
+## Adding an Option
+
+This is cool and it (mostly) works, but I want to add a few features. First, I'd like to add a "Cancel" option to make it easy for a user (i.e., me) to change their mind and do nothing.
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ OPTIONS=$(jq -r '.scripts|to_entries[]|((.key))' package.json)
+ OPTIONS+="\n๐ Cancel"
+ SELECTION=$(echo $OPTIONS | gum choose --header "Choose a script to run")
+
+ if [[ "$SELECTION" == "๐ Cancel" ]]; then
+ gum log --level info "Execution was cancelled"
+ return 1
+ else
+ npm run $SELECTION
+ fi
+}
+
+# loads more stuff
+```
+
+Now, I've split things up a bit differently. I need to separate the steps of grabbing the scripts from `package.json` and piping them into `gum choose`. I'm doing this to give me the opportunity to add an option of my own. I capture to the `OPTIONS` variable and then add my "Cancel" option to that variable. Then I can echo the whole thing back to pipe it into `gum choose`.
+
+This also requires me to make a special case: if the selection is the "Cancel" option, I log out a message (using `gum log`) and exit with a 1 since nothing was executed here. Not sure if that's the right move since the command was actually "successful" in doing what the user wanted, which happened to be nothing, but it should be fine. In any other case, I run the selection through `npm run` just like before.
+
+## Yeah, but What Happens When I REALLY Forget?
+
+It's mostly good at this point, but there's one more key piece I want to get in here. This whole thing is about me forgetting. So, what happens if I think a project is an npm project and try to run `donpm` even though it isn't? Maybe it's a Python project or maybe I'm just in the directory where I keep scans of old video game magazines. Let's handle that.
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ if [ ! -f "package.json" ]; then
+ gum log --level error "package.json not found in the current directory"
+ return 1
+ fi
+
+ OPTIONS=$(jq -r '.scripts|to_entries[]|((.key))' package.json)
+ OPTIONS+="\n๐ Cancel"
+ SELECTION=$(echo $OPTIONS | gum choose --header "Choose a script to run")
+
+ if [[ "$SELECTION" == "๐ Cancel" ]]; then
+ gum log --level info "Execution was cancelled"
+ return 1
+ else
+ npm run $SELECTION
+ fi
+}
+
+# loads more stuff
+```
+
+This gives me some safety so I can fully forget almost everything I know and still at least get a solid error message out of this function.
+
+## The Final Product
+
+After all of these improvements, here's what my workflow looks like when I come back to a project I've forgotten everything I ever knew about:
+
+
+
+## Can it Get Any Better Than This?
+
+Well, yes, of course it can! What you're looking at is the function I'm using now, but I have some ideas for improvements I may make someday if motivation strikes:
+
+- Add "descriptions" to each script in the form of the commands being run.
+- Filter out the `pre` and `post` lifecycle hook scripts since these aren't usually invoked manually.
+- Accept an optional argument of the script name. This would allow Don to entirely take the place of `npm run`, saving me two characters per script invocation if I happen to recall the script name I want to run! If the function gets an argument, just run the npm script named by that argument. If not, show the choices just like it does now.
+
+Feel free to take those on yourself if you're one of those "students surpassing the teacher" types.
+
+## Bonus: VHS
+
+I spend a lot of time in the terminal, and since that's Charm's whole game, I poked around to see all the tools they have available. Another one I found intriguing aside from `gum` was [VHS](https://github.com/charmbracelet/vhs).
+
+VHS lets you build programmatic GIF animations of terminal activity. No more screen recording the terminal emulator. Cool! So cool, in fact, I used it for the GIFs you've seen in this post.
+
+If you want to see how these are defined, check out the code for the two that appear here:
+
+- [`npm run` script discovery method `.tape`](https://codeberg.org/RadDevon/devon.lol/src/commit/fa056712d1d3f169c2f37b61b48bd628f753562e/images/blog/outsourcing-my-memory-to-gum/script-discovery.tape)
+- [New method `.tape`](https://codeberg.org/RadDevon/devon.lol/src/commit/fa056712d1d3f169c2f37b61b48bd628f753562e/images/blog/outsourcing-my-memory-to-gum/donpm.tape)
diff --git a/src/feed/outsourcing-my-memory-to-gum.md b/src/feed/outsourcing-my-memory-to-gum.md
new file mode 100644
index 0000000..4dc0941
--- /dev/null
+++ b/src/feed/outsourcing-my-memory-to-gum.md
@@ -0,0 +1,127 @@
+---
+title: Outsourcing my memory to Gum
+relativeUrl: /blog/outsourcing-my-memory-to-gum
+date: git Created
+tags: feed
+---
+
+My memory is notoriously bad. I forgot what I ate today or whether I washed my hair. I walk into rooms and forget why. I tell friends stories they were themselves involved in.
+
+You better believe that, when I return to some sort of web development project after several months, I've long since forgotten how to do anything with it. This is compounded by the fact that every tech stack has their own default way of doing things. For example, some npm project bootstrappers will create a script called `start` while others will create one called `develop`, both of which are used to start a local development server. I will sometimes add scripts for various types of deployment, which are different depending on the project, the tech stack it's on, where it's deployed, and how good I am at naming things on that day. ๐
+
+# My Crappy Workaround
+
+I usually work around these problems on Node-based projects by starting with `npm run` when I return to a project to get an overview of the project's scripts so I can understand how to jump in.
+
+
+
+(Confession time: in truth, I `cat package.json`, but `npm run` is what I _should_ have been doing instead.)
+
+This is cumbersome and clunky though. I'm manually looking at a file or at some output based on a file to figure out what options I have. Then, I manually run the option that does what I'm trying to do. The data that I'm looking at is already nicely structured in JSON, so it seems like a natural fit for the computer to handle the first part for me and just present me with options I can run directly. Now, let's see how to make that happenโฆ
+
+# A Cleaner Approach with Gum
+
+That's when I stumbled upon [Charm's](https://charm.sh/blog/) [Gum](https://github.com/charmbracelet/gum). Gum is basically a UI component library for shell scripts. Gum has [a `choose` command](https://github.com/charmbracelet/gum?tab=readme-ov-file#choose) that outputs a handy option chooser component. This would be a great UI for my use case. I will need to pipe the available options into it and then run the result. Let's start putting the pieces together.
+
+I would like a single command I can run from any npm project. To accomplish this, I'll write a function into my shell's config. In my case, that's `~/.zshrc`. I call the function `donpm` which you can interpret as either "do npm" or "Don P.M." who I imagine as a cool person with aviator shades who only comes out at night (yes, even with the shades; they're _that_ cool) and can figure out exactly how all your npm projects work. ๐
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ $SELECTION=(jq -r '.scripts|to_entries[]|((.key))' package.json | gum choose)
+ npm run $SELECTION
+}
+
+# loads more stuff
+```
+
+This function has a dependency on `jq`. I could probably do this with `sed`, but it's easier with `jq` and this is just for me to run on my system so I'm going to use it. With `jq`, I'm reading the contents of `package.json` and parsing out the scripts. I pipe that over to `gum choose` to display the chooser component.
+
+`gum` will spit back the selection. I need to capture that so I can run the selected command, hence assigning the result to a variable (`SELECTION`).
+
+Once I have that captured, I can then run `npm run $SELECTION` to interpolate the selected command into my `npm run`. (I tried piping to this, but that didn't seem to work. Maybe there's a fancier, better way, but this one seems to work well enoughโฆ well, almost. ๐)
+
+# Adding an Option
+
+This is cool and it (mostly) works, but I want to add a few features. First, I'd like to add a "Cancel" option to make it easy for a user (i.e., me) to change their mind and do nothing.
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ OPTIONS=$(jq -r '.scripts|to_entries[]|((.key))' package.json)
+ OPTIONS+="\n๐ Cancel"
+ SELECTION=$(echo $OPTIONS | gum choose --header "Choose a script to run")
+
+ if [[ "$SELECTION" == "๐ Cancel" ]]; then
+ gum log --level info "Execution was cancelled"
+ return 1
+ else
+ npm run $SELECTION
+ fi
+}
+
+# loads more stuff
+```
+
+Now, I've split things up a bit differently. I need to separate the steps of grabbing the scripts from `package.json` and piping them into `gum choose`. I'm doing this to give me the opportunity to add an option of my own. I capture to the `OPTIONS` variable and then add my "Cancel" option to that variable. Then I can echo the whole thing back to pipe it into `gum choose`.
+
+This also requires me to make a special case: if the selection is the "Cancel" option, I log out a message (using `gum log`) and exit with a 1 since nothing was executed here. Not sure if that's the right move since the command was actually "successful" in doing what the user wanted, which happened to be nothing, but it should be fine. In any other case, I run the selection through `npm run` just like before.
+
+# Yeah, but What Happens When I REALLY Forget?
+
+It's mostly good at this point, but there's one more key piece I want to get in here. This whole thing is about me forgetting. So, what happens if I think a project is an npm project and try to run `donpm` even though it isn't? Maybe it's a Python project or maybe I'm just in the directory where I keep scans of old video game magazines. Let's handle that.
+
+```bash
+# loads of other stuff
+
+function donpm() {
+ if [ ! -f "package.json" ]; then
+ gum log --level error "package.json not found in the current directory"
+ return 1
+ fi
+
+ OPTIONS=$(jq -r '.scripts|to_entries[]|((.key))' package.json)
+ OPTIONS+="\n๐ Cancel"
+ SELECTION=$(echo $OPTIONS | gum choose --header "Choose a script to run")
+
+ if [[ "$SELECTION" == "๐ Cancel" ]]; then
+ gum log --level info "Execution was cancelled"
+ return 1
+ else
+ npm run $SELECTION
+ fi
+}
+
+# loads more stuff
+```
+
+This gives me some safety so I can fully forget almost everything I know and still at least get a solid error message out of this function.
+
+# The Final Product
+
+After all of these improvements, here's what my workflow looks like when I come back to a project I've forgotten everything I ever knew about:
+
+
+
+# Can it Get Any Better Than This?
+
+Well, yes, of course it can! What you're looking at is the function I'm using now, but I have some ideas for improvements I may make someday if motivation strikes:
+
+- Add "descriptions" to each script in the form of the commands being run.
+- Filter out the `pre` and `post` lifecycle hook scripts since these aren't usually invoked manually.
+- Accept an optional argument of the script name. This would allow Don to entirely take the place of `npm run`, saving me two characters per script invocation if I happen to recall the script name I want to run! If the function gets an argument, just run the npm script named by that argument. If not, show the choices just like it does now.
+
+Feel free to take those on yourself if you're one of those "students surpassing the teacher" types.
+
+# Bonus: VHS
+
+I spend a lot of time in the terminal, and since that's Charm's whole game, I poked around to see all the tools they have available. Another one I found intriguing aside from `gum` was [VHS](https://github.com/charmbracelet/vhs).
+
+VHS lets you build programmatic GIF animations of terminal activity. No more screen recording the terminal emulator. Cool! So cool, in fact, I used it for the GIFs you've seen in this post.
+
+If you want to see how these are defined, check out the code for the two that appear here:
+
+- [`npm run` script discovery method `.tape`](https://codeberg.org/RadDevon/devon.lol/src/commit/fa056712d1d3f169c2f37b61b48bd628f753562e/images/blog/outsourcing-my-memory-to-gum/script-discovery.tape)
+- [New method `.tape`](https://codeberg.org/RadDevon/devon.lol/src/commit/fa056712d1d3f169c2f37b61b48bd628f753562e/images/blog/outsourcing-my-memory-to-gum/donpm.tape)
diff --git a/src/index.md b/src/index.md
index 30e8d7c..6aeb3de 100644
--- a/src/index.md
+++ b/src/index.md
@@ -7,6 +7,6 @@ date: git Last Modified
**Last updated {{ page.date | formatDatetimeForHumans }}**
-This site now both [sends and receives WebMentions](/blog/implementing-webmentions-is-a-pain)!
+I sometimes work on web dev projects that I leave behind. When I return, I don't remember how to use them anymore. I try to keep all of the things I need to do with them in npm scripts so that at least I can rediscover how they work, but actually doing so is clunky. [I found a cool tool and wrote a shell function that makes this process much nicer.](/blog/outsourcing-my-memory-to-gum)
-The Arc browser scares me. All the AI features and their insistence on referring to users as "members" just makes me feel like they're poised to make changes I couldn't live with. I decided to get ahead of them and [find an alternative browser](/blog/web-browser-review-06-2024).
+Also, this site now both [sends and receives WebMentions](/blog/implementing-webmentions-is-a-pain)!