#!/usr/bin/env bash set -euo pipefail ############################################################################### # KRIS CREATE — Content creator for krisyotam.com # # Maintainer: Kris Yotam # License: MIT # Created: 2026-02-15 # Description: Create new content entries. Collects title + preview from user, # auto-generates globally unique slug, then delegates metadata # decisions (category, tags, status, etc.) to Claude. ############################################################################### ############################################################################### # config ############################################################################### CONTENT_DB="/home/krisyotam/dev/krisyotam.com/public/data/content.db" SYSTEM_DB="/home/krisyotam/dev/krisyotam.com/public/data/system.db" GENERATE_SCRIPT="/home/krisyotam/dev/krisyotam.com/public/scripts/keep/generateMetadata.js" CONTENT_DIR="$HOME/content" ALL_TYPES=( papers blog essays notes diary progymnasmata reviews til now ) # Content tables in content.db to check for slug collisions CONTENT_TABLES=( blog diary essays fiction news notes ocs papers progymnasmata reviews verse ) # System tables in system.db to check for slug collisions SYSTEM_TABLES=(til now) ############################################################################### # helpers ############################################################################### have_cmd() { command -v "$1" >/dev/null 2>&1; } die() { echo "ERROR: $1" >&2; exit 1; } have_cmd fzf || die "fzf not found" have_cmd sqlite3 || die "sqlite3 not found" have_cmd claude || die "claude CLI not found" have_cmd node || die "node not found" [[ -f "$CONTENT_DB" ]] || die "content.db not found at $CONTENT_DB" [[ -f "$GENERATE_SCRIPT" ]] || die "generateMetadata.js not found" sql_content() { sqlite3 "$CONTENT_DB" "$1"; } sql_system() { sqlite3 "$SYSTEM_DB" "$1" 2>/dev/null; } ############################################################################### # ui ############################################################################### print_banner() { local cols cols="$(tput cols 2>/dev/null || echo 80)" if have_cmd figlet; then figlet -w "$cols" -f big "CREATE" 2>/dev/null \ || figlet -w "$cols" "CREATE" else echo "=== CREATE ===" fi echo echo " New content entry for krisyotam.com" echo " Metadata determined by Claude after analysis." echo } ############################################################################### # slug generation + validation ############################################################################### # Convert title to URL-safe slug slugify() { echo "$1" | tr '[:upper:]' '[:lower:]' | # lowercase sed 's/[^a-z0-9 -]//g' | # strip special chars sed 's/ */ /g' | # collapse spaces sed 's/ /-/g' | # spaces to hyphens sed 's/--*/-/g' | # collapse hyphens sed 's/^-//;s/-$//' # trim leading/trailing hyphens } # Check if slug exists in ANY content table (content.db) or system table (system.db) # Returns: "type:title" if found, empty if unique check_slug_globally() { local slug="$1" # Check all content.db tables for table in "${CONTENT_TABLES[@]}"; do local result result="$(sql_content "SELECT '$table' || ':' || title FROM $table WHERE slug='$slug' LIMIT 1" 2>/dev/null)" || continue if [[ -n "$result" ]]; then echo "$result" return fi done # Check system.db tables for table in "${SYSTEM_TABLES[@]}"; do local result result="$(sql_system "SELECT '$table' || ':' || title FROM $table WHERE slug='$slug' LIMIT 1" 2>/dev/null)" || continue if [[ -n "$result" ]]; then echo "$result" return fi done } ############################################################################### # slug validation loop ############################################################################### validate_slug() { local slug_var="$1" local slug="${!slug_var}" while true; do local collision collision="$(check_slug_globally "$slug")" if [[ -z "$collision" ]]; then printf -v "$slug_var" '%s' "$slug" echo " Slug: $slug" return 0 fi echo echo " COLLISION: slug '$slug' already exists in: $collision" echo " Enter alternative slug (or 'q' to cancel):" read -rp " slug> " slug [[ "$slug" == "q" ]] && return 1 [[ -z "$slug" ]] && return 1 # Re-slugify in case user typed something with spaces slug="$(slugify "$slug")" done } ############################################################################### # type-specific handlers ############################################################################### handle_til() { echo echo " === Create TIL (Today I Learned) ===" echo read -rp " Title: " title [[ -z "$title" ]] && { echo " Title required."; return 1; } echo echo " Enter content (type END on a new line when done):" local content="" while IFS= read -r line; do [[ "$line" == "END" ]] && break content+="$line"$'\n' done [[ -z "$content" ]] && { echo " Content required."; return 1; } local slug slug="$(slugify "$title")" validate_slug slug || return 1 # TIL goes directly to generateMetadata.js, no Claude needed echo echo " Creating TIL entry..." node "$GENERATE_SCRIPT" --type til --title "$title" --slug "$slug" --content "$content" } handle_now() { echo echo " === Create Now Update ===" echo echo " Enter content (type END on a new line when done):" local content="" while IFS= read -r line; do [[ "$line" == "END" ]] && break content+="$line"$'\n' done [[ -z "$content" ]] && { echo " Content required."; return 1; } local slug slug="$(date +%m-%Y)" echo echo " Creating Now entry..." node "$GENERATE_SCRIPT" --type now --title "Now — $(date +%B\ %Y)" --slug "$slug" --content "$content" } handle_review() { local title="$1" slug="$2" preview="$3" # Reviews need a rating local rating="" while true; do read -rp " Rating (1-10): " rating if [[ "$rating" =~ ^[0-9]+$ ]] && (( rating >= 1 && rating <= 10 )); then break fi echo " Invalid. Must be 1-10." done build_and_delegate "$slug" "reviews" "$title" "$preview" "--rating $rating" } handle_standard() { local type="$1" title="$2" slug="$3" preview="$4" build_and_delegate "$slug" "$type" "$title" "$preview" "" } ############################################################################### # claude delegation ############################################################################### build_and_delegate() { local slug="$1" type="$2" title="$3" preview="$4" extra_flags="$5" local date date="$(date +%Y-%m-%d)" local script_path="node $GENERATE_SCRIPT" local instruction="You are creating a new $type entry. Analyze the content and determine appropriate metadata following the taxonomy rules in CLAUDE.md. === USER INPUT === Type: $type Title: $title Preview: ${preview:-"(none)"} Slug: $slug Date: $date" if [[ "$type" == "diary" ]]; then instruction+=" === YOUR TASK === 1. ANALYZE the title and preview to understand the subject matter. 2. DETERMINE: - Category: Choose from global categories - Tags: Select 3+ relevant tags (MUST use existing tags only for diary entries) 3. CREATE the entry using this command: $script_path --type diary --title \"$title\" --slug \"$slug\" --preview \"${preview:-""}\" --category --tags \"\"" elif [[ "$type" == "progymnasmata" ]]; then instruction+=" === YOUR TASK === 1. ANALYZE the title and preview. 2. DETERMINE: - Category: Choose from progymnasmata exercises (chreia, commonplace, comparison, confirmation, description, encomium, fable, impersonation, introduction-of-a-law, maxim, narrative, refutation, thesis, vituperation) - Tags: Select 3+ relevant tags - Status, Certainty, Importance 3. CREATE the entry using this command: $script_path --type progymnasmata --title \"$title\" --slug \"$slug\" --preview \"${preview:-""}\" --category --tags \"\" --status --certainty --importance <1-10>" elif [[ "$type" == "reviews" ]]; then instruction+=" === YOUR TASK === 1. ANALYZE the title and preview. 2. DETERMINE: - Category: Choose media type (anime, book, bookstores, film, manga, television) - Tags: Select 3+ relevant tags - Status, Certainty, Importance 3. CREATE the entry using this command: $script_path --type reviews --title \"$title\" --slug \"$slug\" --preview \"${preview:-""}\" --category --tags \"\" --status --certainty --importance <1-10> $extra_flags" else instruction+=" === YOUR TASK === 1. ANALYZE the title and preview to understand the subject matter. 2. DETERMINE: - Category: Choose from global categories (culture, film, history, literature, philosophy, psychology, science, technology, theology, on-myself, manuals-of-style, website) - Tags: Select 3+ relevant tags following taxonomy rules - Status: Assess completion (Notes, Draft, In Progress, Finished, Abandoned) - Certainty: How verifiable (certain to impossible) - Importance: Rate 1-10 3. CREATE the entry using this command: $script_path --type $type --title \"$title\" --slug \"$slug\" --preview \"${preview:-""}\" --category --tags \"\" --status --certainty --importance <1-10>" fi instruction+=" 4. VERIFY the entry was created successfully. Execute this now without asking for confirmation. Make thoughtful metadata choices based on the content." echo echo " === Calling Claude Code ===" echo " Claude will analyze your content and determine metadata..." echo claude -p "$instruction" } ############################################################################### # main ############################################################################### main() { clear print_banner # Select content type local type type="$( printf "%s\n" "${ALL_TYPES[@]}" "quit" | fzf --prompt="type> " --height=15 --reverse )" || exit 0 [[ "$type" == "quit" ]] && exit 0 # Handle TIL and Now specially (they collect content, not just title/preview) case "$type" in til) handle_til; exit $? ;; now) handle_now; exit $? ;; esac # Standard flow: title -> slug -> preview echo echo " === Create ${type^} Entry ===" echo read -rp " Title: " title [[ -z "$title" ]] && { echo " Title required."; exit 1; } local slug slug="$(slugify "$title")" # Validate slug globally validate_slug slug || exit 1 read -rp " Preview/description: " preview # Route to handler case "$type" in reviews) handle_review "$title" "$slug" "$preview" ;; *) handle_standard "$type" "$title" "$slug" "$preview" ;; esac echo echo " === Done ===" } main