~kris/dots

srice

ref: e9b48d06a8541f3eda5c4db90382ab3c77183afb srice/.local/bin/misc/create -rwxr-xr-x 11.3 KiB
e9b48d06 — Kris Yotam xprofile: systemd-aware pipewire start + blueman-applet; sb-internet: tolerate missing /proc/net/wireless 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
#!/usr/bin/env bash
set -euo pipefail

###############################################################################
# KRIS CREATE — Content creator for krisyotam.com
#
# Maintainer:   Kris Yotam <krisyotam@pm.me>
# 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 <chosen-category> --tags \"<tag1,tag2,tag3>\""

    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 <chosen-category> --tags \"<tag1,tag2,tag3>\" --status <status> --certainty <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 <chosen-category> --tags \"<tag1,tag2,tag3>\" --status <status> --certainty <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 <chosen-category> --tags \"<tag1,tag2,tag3>\" --status <status> --certainty <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