This is a bit esoteric, but I’m writing it down here anyway.

I have switched between using Denote and Org-roam for my Org-mode notes several times. They are mostly compatible, so this hasn’t been too troublesome.

One thing I needed to do was make sure that all the .org files included an :ID: property at the top so that Org-roam includes them in its database. The ID property looks like this:

:PROPERTIES:
:ID:       4def7046-3ef8-4436-a079-09362bf66aff
:END:

Many of my Denote files already include this because I like to drag and drop files and images into the notes, and the ID property makes this work correctly. But there were maybe 300 files without an ID, and I had no desire to add the property manually to every one of them.

I started to write a shell script to do this, but got hung up on some bit of syntax, so I searched for the error and found the fix. Then I had trouble with the sed command, etc. 

Sometimes I forget that ChatGPT exists. Sometimes I ignore it because I want to learn stuff on my own. But sometimes I just want the answer, and, problematic as LLMs can be, there’s no denying their usefulness.

A couple of prompts, a few iterations, and I had what I needed in about 10 minutes.

#!/bin/bash

# Directory containing the Org-mode files
ORG_DIR="${1:-.}" # Defaults to current directory if not provided

# Loop through all .org files in the specified directory
for file in "$ORG_DIR"/*.org; do
	# Skip if no Org-mode files are found
	[ -e "$file" ] || continue

	# Check if the :ID: property exists in the first 5 lines
	if ! head -n 5 "$file" | grep -q "^:ID:"; then
		# Generate a unique ID (UUID)
		id=$(uuidgen)

		# Create the :PROPERTIES: block with the :ID:
		properties_block=":PROPERTIES:\n:ID: $id\n:END:"

		# Insert the :PROPERTIES: block at the very top of the file
		gsed -i "1i $properties_block" "$file"

		echo "Added :ID: $id inside a :PROPERTIES: block to $file"
	else
		echo ":ID: already exists in the first 5 lines of $file"
	fi
done

The only change I needed to make was to substitute  gsed for sedbecause the macOS version of sed was missing a switch and throwing an error.

While I was in there, I asked ChatGPT for a version of the script in Emacs lisp, just in case. I had it write the function for use in a Dired buffer, since that’s where I’m most likely to want it. Here’s the function…

(defun add-id-to-org-files-in-dired ()
  "Add :ID: property inside a :PROPERTIES: block at the top of all Org files in the current Dired buffer."
  (interactive)
  ;;(require 'uuidgen) ;; Ensure `uuidgen` is available
  (dired-map-dired-file-lines
   (lambda (file)
	 (when (and (string-suffix-p ".org" file t) (file-exists-p file))
	   (with-temp-buffer
		 (insert-file-contents file)
		 ;; Check the first 5 lines for an existing :ID: property
		 (unless (save-excursion
				   (goto-char (point-min))
				   (search-forward ":ID:" (line-end-position 5) t))
		   ;; Insert the :PROPERTIES: block at the top of the file
		   (goto-char (point-min))
		   (insert ":PROPERTIES:\n:ID: " (org-id-uuid) "\n:END:\n\n")
		   (write-region (point-min) (point-max) file))
		 (message "Added :ID: to %s" file))))))

Done and done. There may be better, cleaner ways to do this, but my problem is solved, which is all I wanted.