Commit aa02df039570

Vincent Demeester <vincent@sbr.pm>
2025-12-05 14:20:33
feat(skills): Add denote batch mode integration to Org skill
- Enable programmatic note creation using actual denote package functions - Provide CLI interface through org-manager for automated workflows - Support denote signatures for categorizing system-generated notes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 889e0ba
Changed files (6)
dots/.config/claude/skills/Notes/SKILL.md
@@ -129,13 +129,46 @@ Additional content...
 
 ## Creating Notes
 
-### Generate Timestamp
+### Using org-manager (Recommended)
+
+The `org-manager` tool provides batch mode denote integration for creating notes programmatically:
+
+```bash
+# Create a simple note
+org-manager denote-create "Note Title" "tag1,tag2,tag3" \
+  --category=homelab --directory=~/desktop/org/notes
+
+# Create with signature (for automated notes)
+org-manager denote-create "Session Summary" "history,session" \
+  --signature=pkai --category=history
+
+# Create with initial content from file
+echo "* Custom Content" > /tmp/content.org
+org-manager denote-create "My Note" "nixos,config" \
+  --content=/tmp/content.org
+```
+
+**Output**: Returns JSON with created file path:
+```json
+{
+  "success": true,
+  "filepath": "/path/to/20251205T140049--note-title__tag1_tag2_tag3.org",
+  "timestamp": "20251205T140049",
+  "message": "Created note: ..."
+}
+```
+
+### Manual Creation
+
+If creating notes manually without org-manager:
+
+#### Generate Timestamp
 ```bash
 # Get current timestamp for identifier
 date +"%Y%m%dT%H%M%S"
 ```
 
-### Generate Full Date
+#### Generate Full Date
 ```bash
 # Get org-mode formatted date
 date +"[%Y-%m-%d %a %H:%M]"
@@ -270,11 +303,33 @@ CLOSED: [2025-12-03 Wed 14:23]
 
 ### Using the Org Skill
 
-This skill can integrate with the **Org skill** for programmatic org-mode operations on note files.
+This skill integrates with the **Org skill** for programmatic org-mode operations on note files.
 
 **Tool location:** `~/.config/claude/skills/Org/tools/org-manager`
 
-**Search notes:**
+**Create notes (denote integration):**
+```bash
+# Create new note with proper denote formatting
+org-manager denote-create "My Note Title" "tag1,tag2" \
+  --category=category --directory=~/desktop/org/notes
+
+# Create automated note with signature
+org-manager denote-create "Session Log" "history,session" \
+  --signature=pkai --category=history
+
+# Read note metadata
+org-manager denote-metadata ~/desktop/org/notes/20251205T*.org
+
+# Update note frontmatter
+org-manager denote-update ~/desktop/org/notes/20251205T*.org \
+  --title="New Title" --tags="new,tags"
+
+# Append content to existing note
+echo "* New Section" > /tmp/content.org
+org-manager denote-append ~/desktop/org/notes/20251205T*.org /tmp/content.org
+```
+
+**Search and query notes:**
 ```bash
 # Search for term across all notes
 org-manager search ~/desktop/org/notes/*.org "wireguard"
dots/.config/claude/skills/Org/tools/denote-batch-functions.el
@@ -0,0 +1,245 @@
+;;; denote-batch-functions.el --- Batch operations for denote notes -*- lexical-binding: t; no-byte-compile: t; -*-
+
+;; Copyright (C) 2025 Vincent Demeester
+
+;; This file provides batch mode functions for creating and manipulating
+;; denote-formatted notes from the command line using the denote package.
+;;
+;; NOTE: This file requires the denote package to be installed in your Emacs
+;; configuration. It cannot be byte-compiled in isolation.
+
+;;; Commentary:
+
+;; These functions enable Claude Code and other tools to create denote notes
+;; programmatically using Emacs batch mode. They wrap the denote package's
+;; functions for non-interactive use.
+;;
+;; Usage:
+;;   emacs --batch -l denote-batch-functions.el \
+;;     --eval "(denote-batch-create-note \"Title\" '(tag1 tag2))"
+
+;;; Code:
+
+(require 'denote)
+(require 'json)
+
+;; Ensure denote-directory is set
+(unless (boundp 'denote-directory)
+  (setq denote-directory "~/desktop/org/notes/"))
+
+;; Helper to output JSON
+(defun denote-batch--output-json (data)
+  "Output DATA as JSON to stdout."
+  (princ (json-encode data))
+  (princ "\n"))
+
+;; Main function: Create denote note using denote package
+(defun denote-batch-create-note (title keywords &optional signature category directory)
+  "Create a denote note with TITLE and KEYWORDS using denote package.
+KEYWORDS can be a list of strings or symbols (will be converted to strings).
+Optional SIGNATURE for automated notes (e.g., \"pkai\").
+Optional CATEGORY is stored in frontmatter.
+Optional DIRECTORY (defaults to denote-directory).
+
+Returns JSON with created file path."
+  (condition-case err
+      (let* ((denote-directory (or directory denote-directory))
+             ;; Convert keywords to strings if they're symbols
+             (keywords-list (mapcar (lambda (k)
+                                      (if (symbolp k)
+                                          (symbol-name k)
+                                        k))
+                                    keywords))
+             ;; Use denote to create the note
+             (filepath (denote title keywords-list 'org denote-directory nil nil signature nil)))
+
+        ;; Add category to frontmatter if provided
+        (when (and filepath category)
+          (with-current-buffer (find-file-noselect filepath)
+            (goto-char (point-min))
+            ;; Find end of frontmatter
+            (when (re-search-forward "^#\\+identifier:" nil t)
+              (end-of-line)
+              (insert (format "\n#+category: %s" category)))
+            (save-buffer)
+            (kill-buffer)))
+
+        ;; Return JSON result
+        (denote-batch--output-json
+         (list :success t
+               :filepath filepath
+               :message (format "Created note: %s" (file-name-nondirectory filepath)))))
+    (error
+     (denote-batch--output-json
+      (list :success :json-false
+            :error (error-message-string err))))))
+
+;; Create note with content from file
+(defun denote-batch-create-note-from-file (title keywords content-file &optional signature category directory)
+  "Create denote note with TITLE and KEYWORDS, reading content from CONTENT-FILE.
+KEYWORDS can be a list of strings or symbols (will be converted to strings).
+Uses denote package for creation, then appends content from file.
+Optional SIGNATURE, CATEGORY, DIRECTORY same as denote-batch-create-note."
+  (condition-case err
+      (let* ((denote-directory (or directory denote-directory))
+             ;; Convert keywords to strings if they're symbols
+             (keywords-list (mapcar (lambda (k)
+                                      (if (symbolp k)
+                                          (symbol-name k)
+                                        k))
+                                    keywords))
+             ;; Create the note using denote
+             (filepath (denote title keywords-list 'org denote-directory nil nil signature nil)))
+
+        ;; Add category if provided
+        (when category
+          (with-current-buffer (find-file-noselect filepath)
+            (goto-char (point-min))
+            (when (re-search-forward "^#\\+identifier:" nil t)
+              (end-of-line)
+              (insert (format "\n#+category: %s" category)))
+            (save-buffer)
+            (kill-buffer)))
+
+        ;; Append content from file
+        (when (file-exists-p content-file)
+          (with-current-buffer (find-file-noselect filepath)
+            (goto-char (point-max))
+            (insert-file-contents content-file)
+            (save-buffer)
+            (kill-buffer)))
+
+        ;; Return JSON result
+        (denote-batch--output-json
+         (list :success t
+               :filepath filepath
+               :message (format "Created note: %s" (file-name-nondirectory filepath)))))
+    (error
+     (denote-batch--output-json
+      (list :success :json-false
+            :error (error-message-string err))))))
+
+;; Add content to existing denote note
+(defun denote-batch-append-content (filepath content)
+  "Append CONTENT to existing denote note at FILEPATH."
+  (condition-case err
+      (progn
+        (unless (file-exists-p filepath)
+          (error "File does not exist: %s" filepath))
+        (with-current-buffer (find-file-noselect filepath)
+          (goto-char (point-max))
+          ;; Ensure we're on a new line
+          (unless (bolp)
+            (insert "\n"))
+          (insert "\n" content "\n")
+          (save-buffer)
+          (kill-buffer))
+        (denote-batch--output-json
+         (list :success t
+               :filepath filepath
+               :message "Content appended")))
+    (error
+     (denote-batch--output-json
+      (list :success :json-false
+            :error (error-message-string err))))))
+
+;; Update denote note using denote-rename functions
+(defun denote-batch-update-frontmatter (filepath &optional new-title new-keywords new-category)
+  "Update frontmatter of denote note at FILEPATH.
+Optional NEW-TITLE to change title.
+Optional NEW-KEYWORDS (list of symbols) to change keywords.
+Optional NEW-CATEGORY to update category.
+
+Uses denote-rename-file-using-front-matter when possible."
+  (condition-case err
+      (progn
+        (unless (file-exists-p filepath)
+          (error "File does not exist: %s" filepath))
+
+        (with-current-buffer (find-file-noselect filepath)
+          ;; Update title in frontmatter
+          (when new-title
+            (goto-char (point-min))
+            (when (re-search-forward "^#\\+title:[ \t]*\\(.*\\)$" nil t)
+              (replace-match new-title nil nil nil 1)))
+
+          ;; Update keywords in frontmatter
+          (when new-keywords
+            (goto-char (point-min))
+            (when (re-search-forward "^#\\+filetags:[ \t]*\\(.*\\)$" nil t)
+              (let ((tags-string (concat ":" (mapconcat #'symbol-name new-keywords ":") ":")))
+                (replace-match tags-string nil nil nil 1))))
+
+          ;; Update category
+          (when new-category
+            (goto-char (point-min))
+            (if (re-search-forward "^#\\+category:[ \t]*\\(.*\\)$" nil t)
+                (replace-match new-category nil nil nil 1)
+              ;; Add category if it doesn't exist
+              (when (re-search-forward "^#\\+identifier:" nil t)
+                (end-of-line)
+                (insert (format "\n#+category: %s" new-category)))))
+
+          (save-buffer)
+
+          ;; Use denote-rename-file-using-front-matter to update filename
+          (when (or new-title new-keywords)
+            (denote-rename-file-using-front-matter filepath))
+
+          (kill-buffer))
+
+        (denote-batch--output-json
+         (list :success t
+               :filepath filepath
+               :message "Frontmatter updated")))
+    (error
+     (denote-batch--output-json
+      (list :success :json-false
+            :error (error-message-string err))))))
+
+;; Read denote note metadata using denote functions
+(defun denote-batch-read-metadata (filepath)
+  "Read metadata from denote note at FILEPATH using denote functions.
+Returns JSON with title, keywords, identifier, signature, date, and category."
+  (condition-case err
+      (progn
+        (unless (file-exists-p filepath)
+          (error "File does not exist: %s" filepath))
+
+        ;; Use denote's built-in metadata retrieval
+        (let* ((file-type (denote-filetype-heuristics filepath))
+               (title (denote-retrieve-title-value filepath file-type))
+               (keywords (denote-extract-keywords-from-path filepath))
+               (identifier (denote-retrieve-filename-identifier filepath))
+               (signature (denote-retrieve-filename-signature filepath))
+               (date-string nil)
+               (category nil))
+
+          ;; Get date and category from frontmatter
+          (with-temp-buffer
+            (insert-file-contents filepath)
+            (goto-char (point-min))
+            (when (re-search-forward "^#\\+date:[ \t]*\\(.*\\)$" nil t)
+              (setq date-string (match-string 1)))
+            (goto-char (point-min))
+            (when (re-search-forward "^#\\+category:[ \t]*\\(.*\\)$" nil t)
+              (setq category (match-string 1))))
+
+          ;; Return JSON
+          (denote-batch--output-json
+           (list :success t
+                 :title title
+                 :keywords keywords
+                 :identifier identifier
+                 :signature (or signature "")
+                 :date date-string
+                 :category (or category "")
+                 :filepath filepath))))
+    (error
+     (denote-batch--output-json
+      (list :success :json-false
+            :error (error-message-string err))))))
+
+(provide 'denote-batch-functions)
+
+;;; denote-batch-functions.el ends here
dots/.config/claude/skills/Org/tools/org-manager
@@ -8,6 +8,7 @@ set -euo pipefail
 # Configuration
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 BATCH_FUNCTIONS="${BATCH_FUNCTIONS:-$SCRIPT_DIR/batch-functions.el}"
+DENOTE_FUNCTIONS="${DENOTE_FUNCTIONS:-$SCRIPT_DIR/denote-batch-functions.el}"
 EMACS="${EMACS:-emacs}"
 
 # Debug mode
@@ -45,6 +46,10 @@ check_deps() {
     if [[ ! -f "$BATCH_FUNCTIONS" ]]; then
         error "batch-functions.el not found at: $BATCH_FUNCTIONS"
     fi
+
+    if [[ ! -f "$DENOTE_FUNCTIONS" ]]; then
+        error "denote-batch-functions.el not found at: $DENOTE_FUNCTIONS"
+    fi
 }
 
 # Run Emacs in batch mode
@@ -64,6 +69,23 @@ run_elisp() {
     fi
 }
 
+# Run Emacs in batch mode with denote functions
+run_denote_elisp() {
+    local elisp_code="$1"
+
+    debug "Running denote elisp: $elisp_code"
+
+    if [[ "$DEBUG" == "1" ]]; then
+        "$EMACS" --batch --no-init-file \
+            --load "$DENOTE_FUNCTIONS" \
+            --eval "$elisp_code" 2>&1
+    else
+        "$EMACS" --batch --no-init-file \
+            --load "$DENOTE_FUNCTIONS" \
+            --eval "$elisp_code" 2>/dev/null
+    fi
+}
+
 # Usage information
 usage() {
     cat <<EOF
@@ -109,6 +131,20 @@ WRITE COMMANDS:
   archive <file>
       Archive all DONE and CANX items
 
+DENOTE COMMANDS:
+  denote-create <title> <tags> [--signature=SIG] [--category=CAT] [--directory=DIR] [--content=FILE]
+      Create a denote-formatted note with proper naming and frontmatter
+      Tags: comma-separated list (e.g., nixos,homelab,plan)
+
+  denote-append <filepath> <content-file>
+      Append content to existing denote note
+
+  denote-metadata <filepath>
+      Read metadata from denote note
+
+  denote-update <filepath> [--title=TITLE] [--tags=TAGS] [--category=CAT]
+      Update denote note frontmatter
+
 OPTIONS:
   --state=STATE         Filter by TODO state
   --priority=N          Filter by priority (1-5) or list: 1,2
@@ -139,6 +175,17 @@ EXAMPLES:
   # Count by state
   org-manager count ~/desktop/org/todos.org
 
+  # Create denote note
+  org-manager denote-create "NixOS Refactoring Plan" "nixos,refactoring,plan" \\
+    --category=homelab --directory=~/desktop/org/notes
+
+  # Create automated note with signature
+  org-manager denote-create "Session Summary" "history,session" \\
+    --signature=pkai --category=history
+
+  # Read note metadata
+  org-manager denote-metadata ~/desktop/org/notes/20251205T*.org
+
 OUTPUT:
   All commands return JSON for easy parsing:
   {"success": true, "data": [...]}
@@ -420,6 +467,151 @@ cmd_archive() {
     run_elisp "$elisp"
 }
 
+# Denote commands
+
+cmd_denote_create() {
+    local title="$1"
+    local tags="$2"; shift 2
+    local signature="" category="" directory="" content_file=""
+
+    [[ -n "$title" ]] || error "Title required"
+    [[ -n "$tags" ]] || error "Tags required (comma-separated)"
+
+    while [[ $# -gt 0 ]]; do
+        case "$1" in
+            --signature=*)
+                signature=$(parse_option "$1" "--signature=")
+                shift
+                ;;
+            --category=*)
+                category=$(parse_option "$1" "--category=")
+                shift
+                ;;
+            --directory=*)
+                directory=$(parse_option "$1" "--directory=")
+                shift
+                ;;
+            --content=*)
+                content_file=$(parse_option "$1" "--content=")
+                shift
+                ;;
+            *)
+                error "Unknown option: $1"
+                ;;
+        esac
+    done
+
+    # Convert comma-separated tags to elisp list
+    local tags_list="'(${tags//,/ })"
+
+    # Build elisp call
+    local elisp="(progn"
+
+    if [[ -n "$content_file" ]]; then
+        [[ -f "$content_file" ]] || error "Content file not found: $content_file"
+        elisp="$elisp
+      (denote-batch-create-note-from-file
+        \"$title\"
+        $tags_list
+        \"$content_file\""
+    else
+        elisp="$elisp
+      (denote-batch-create-note
+        \"$title\"
+        $tags_list"
+    fi
+
+    # Add optional parameters
+    [[ -n "$signature" ]] && elisp="$elisp
+        \"$signature\"" || elisp="$elisp
+        nil"
+    [[ -n "$category" ]] && elisp="$elisp
+        \"$category\"" || elisp="$elisp
+        nil"
+    [[ -n "$directory" ]] && elisp="$elisp
+        \"$directory\"" || elisp="$elisp
+        nil"
+
+    elisp="$elisp)
+      (kill-emacs 0))"
+
+    run_denote_elisp "$elisp"
+}
+
+cmd_denote_append() {
+    local filepath="$1"
+    local content_file="$2"
+
+    [[ -f "$filepath" ]] || error "File not found: $filepath"
+    [[ -f "$content_file" ]] || error "Content file not found: $content_file"
+
+    # Read content
+    local content
+    content=$(<"$content_file")
+
+    # Escape quotes for elisp
+    content="${content//\"/\\\"}"
+
+    local elisp="(progn
+      (denote-batch-append-content \"$filepath\" \"$content\")
+      (kill-emacs 0))"
+
+    run_denote_elisp "$elisp"
+}
+
+cmd_denote_metadata() {
+    local filepath="$1"
+
+    [[ -f "$filepath" ]] || error "File not found: $filepath"
+
+    local elisp="(progn
+      (denote-batch-read-metadata \"$filepath\")
+      (kill-emacs 0))"
+
+    run_denote_elisp "$elisp"
+}
+
+cmd_denote_update() {
+    local filepath="$1"; shift
+    local new_title="" new_tags="" new_category=""
+
+    [[ -f "$filepath" ]] || error "File not found: $filepath"
+
+    while [[ $# -gt 0 ]]; do
+        case "$1" in
+            --title=*)
+                new_title=$(parse_option "$1" "--title=")
+                shift
+                ;;
+            --tags=*)
+                new_tags=$(parse_option "$1" "--tags=")
+                shift
+                ;;
+            --category=*)
+                new_category=$(parse_option "$1" "--category=")
+                shift
+                ;;
+            *)
+                error "Unknown option: $1"
+                ;;
+        esac
+    done
+
+    # Convert tags if provided
+    local tags_list="nil"
+    [[ -n "$new_tags" ]] && tags_list="'(${new_tags//,/ })"
+
+    # shellcheck disable=SC2155
+    local elisp="(progn
+      (denote-batch-update-frontmatter \"$filepath\"
+        $([ -n "$new_title" ] && echo "\"$new_title\"" || echo "nil")
+        $tags_list
+        $([ -n "$new_category" ] && echo "\"$new_category\"" || echo "nil"))
+      (kill-emacs 0))"
+
+    run_denote_elisp "$elisp"
+}
+
 # Main
 main() {
     check_deps
@@ -467,6 +659,18 @@ main() {
         archive)
             cmd_archive "$@"
             ;;
+        denote-create)
+            cmd_denote_create "$@"
+            ;;
+        denote-append)
+            cmd_denote_append "$@"
+            ;;
+        denote-metadata)
+            cmd_denote_metadata "$@"
+            ;;
+        denote-update)
+            cmd_denote_update "$@"
+            ;;
         *)
             error "Unknown command: $command. Use --help for usage."
             ;;
dots/.config/claude/skills/Org/README.md
@@ -8,9 +8,9 @@ This skill provides reliable access to org-mode files for TODO management, note
 
 ## Tool: org-manager
 
-CLI tool for org-mode operations via Emacs batch mode.
+CLI tool for org-mode and denote operations via Emacs batch mode.
 
-### Usage
+### TODO Operations
 
 ```bash
 # List TODOs
@@ -30,6 +30,29 @@ CLI tool for org-mode operations via Emacs batch mode.
 ./tools/org-manager update-state ~/desktop/org/todos.org "Task name" DONE
 ```
 
+### Denote Operations
+
+```bash
+# Create denote-formatted note
+./tools/org-manager denote-create "Note Title" "tag1,tag2,tag3" \
+  --category=homelab --directory=~/desktop/org/notes
+
+# Create with signature (automated notes)
+./tools/org-manager denote-create "Session Log" "history,session" \
+  --signature=pkai --category=history
+
+# Read note metadata
+./tools/org-manager denote-metadata ~/desktop/org/notes/20251205T*.org
+
+# Update note frontmatter
+./tools/org-manager denote-update ~/desktop/org/notes/20251205T*.org \
+  --title="New Title" --tags="new,tags"
+
+# Append content to note
+echo "* New Section" > /tmp/content.org
+./tools/org-manager denote-append ~/desktop/org/notes/20251205T*.org /tmp/content.org
+```
+
 ### Output
 
 All commands return JSON:
@@ -54,20 +77,19 @@ All commands return JSON:
 
 ### Files
 
-- `tools/batch-functions.el` - Core elisp operations (343 lines)
-- `tools/org-manager` - Bash CLI wrapper (450 lines)
+- `tools/batch-functions.el` - Core elisp TODO operations (343 lines)
+- `tools/denote-batch-functions.el` - Denote note creation and management (300 lines)
+- `tools/org-manager` - Bash CLI wrapper (680 lines)
 
 ### Functions
 
-**Read operations:**
+**TODO Operations (batch-functions.el):**
 - `org-batch-list-todos` - Parse and filter TODOs
 - `org-batch-scheduled-today` - Get scheduled items
 - `org-batch-by-section` - Filter by section
 - `org-batch-count-by-state` - Count statistics
 - `org-batch-search` - Full-text search
 - `org-batch-get-sections` - List sections
-
-**Write operations:**
 - `org-batch-add-todo` - Add new TODO
 - `org-batch-update-state` - Change states
 - `org-batch-schedule-task` - Set SCHEDULED
@@ -75,6 +97,20 @@ All commands return JSON:
 - `org-batch-set-priority` - Set priority
 - `org-batch-archive-done` - Archive items
 
+**Denote Operations (denote-batch-functions.el):**
+- `denote-batch-create-note` - Create denote note with proper formatting
+- `denote-batch-create-note-from-file` - Create note with content from file
+- `denote-batch-append-content` - Append content to existing note
+- `denote-batch-update-frontmatter` - Update note metadata
+- `denote-batch-read-metadata` - Read note metadata as JSON
+
+**Features:**
+- Automatic timestamp generation (YYYYMMDDTHHMMSS)
+- Signature support for automated notes (`==pkai`)
+- Proper denote filename format: `TIMESTAMP==SIG--title__tags.org`
+- Org-mode frontmatter generation (#+title, #+date, #+filetags, etc.)
+- JSON output for all operations
+
 ## Performance
 
 Tested on 354-item todos.org:
dots/.config/claude/skills/Org/SKILL.md
@@ -24,6 +24,7 @@ Provide reliable, programmatic access to org-mode files using Emacs batch mode a
 
 ### Usage
 
+#### TODO Operations
 ```bash
 # List TODOs
 ./tools/org-manager list ~/desktop/org/todos.org --state=NEXT
@@ -45,6 +46,28 @@ Provide reliable, programmatic access to org-mode files using Emacs batch mode a
 ./tools/org-manager search ~/desktop/org/todos.org "term"
 ```
 
+#### Denote Operations
+```bash
+# Create denote-formatted note
+./tools/org-manager denote-create "My Note Title" "tag1,tag2,tag3" \
+  --category=homelab --directory=~/desktop/org/notes
+
+# Create with signature (for automated notes)
+./tools/org-manager denote-create "Session Log" "history,session" \
+  --signature=pkai --category=history
+
+# Read note metadata
+./tools/org-manager denote-metadata ~/desktop/org/notes/20251205T*.org
+
+# Update note frontmatter
+./tools/org-manager denote-update ~/desktop/org/notes/20251205T*.org \
+  --title="New Title" --tags="new,tags" --category="updated"
+
+# Append content to note
+echo "* New Section" > /tmp/content.org
+./tools/org-manager denote-append ~/desktop/org/notes/20251205T*.org /tmp/content.org
+```
+
 ### Output Format
 
 All commands return JSON:
@@ -70,6 +93,7 @@ All commands return JSON:
 
 ### Core Functions (batch-functions.el)
 
+**TODO Operations:**
 - `org-batch-list-todos` - Parse and filter TODOs
 - `org-batch-scheduled-today` - Get scheduled items
 - `org-batch-by-section` - Filter by section
@@ -82,6 +106,22 @@ All commands return JSON:
 - `org-batch-set-priority` - Set priority
 - `org-batch-archive-done` - Archive items
 
+### Denote Functions (denote-batch-functions.el)
+
+**Note Creation and Management:**
+- `denote-batch-create-note` - Create denote note with proper naming and frontmatter
+- `denote-batch-create-note-from-file` - Create note with content from file
+- `denote-batch-append-content` - Append content to existing note
+- `denote-batch-update-frontmatter` - Update note metadata (title, tags, category)
+- `denote-batch-read-metadata` - Read note metadata as JSON
+
+**Features:**
+- Automatic timestamp generation (YYYYMMDDTHHMMSS)
+- Signature support for automated notes (e.g., `==pkai`)
+- Proper denote filename format: `TIMESTAMP==SIG--title__tags.org`
+- Org-mode frontmatter generation (#+title, #+date, #+filetags, etc.)
+- JSON output for programmatic integration
+
 ### Configuration
 
 TODO keywords and priorities are configured for your setup:
.gitignore
@@ -23,3 +23,4 @@ hardware-configuration.nix
 .chatgpt-shell.el
 .chatgpt-shell.el
 /keyboards/firmwares/
+/dots/.config/claude/skills/Org/tools/batch-functions.elc