<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="https://riceball.com/wp-content/plugins/pretty-rss-feeds/xslt/pretty-feed.xsl" type="text/xsl" media="screen" ?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Prompts - 🍙</title>
	<atom:link href="https://riceball.com/tag/prompts/feed/" rel="self" type="application/rss+xml" />
	<link>https://riceball.com</link>
	<description></description>
	<lastBuildDate>Fri, 09 Oct 2026 10:51:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://riceball.com/wp-content/uploads/sites/2/2025/10/onigiri-150x150.png</url>
	<title>Prompts - 🍙</title>
	<link>https://riceball.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Thaura &#038; WordPress: layout advice chatbot</title>
		<link>https://riceball.com/2026/10/08/thaura-wordpress-layout-advice-chatbot/</link>
					<comments>https://riceball.com/2026/10/08/thaura-wordpress-layout-advice-chatbot/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 03:55:48 +0000</pubDate>
				<category><![CDATA[Machine Learning]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Prompts]]></category>
		<category><![CDATA[Thaura]]></category>
		<guid isPermaLink="false">https://riceball.com/?p=30722</guid>

					<description><![CDATA[<p><img width="1024" height="1024" src="https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10.jpeg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" decoding="async" fetchpriority="high" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10.jpeg 1024w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-300x300.jpeg 300w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-150x150.jpeg 150w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-768x768.jpeg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p><p>This bot gives advice about how to make layouts in WordPress using the stock tools and features. More on this later, on to the Thaura Projects settings. Thaura Projects is a feature to allow you to create chatbots (and other interactive tools). To create a new Project, click on Project in the navigation, and then [&#8230;]</p>
<p>The post <a href="https://riceball.com/2026/10/08/thaura-wordpress-layout-advice-chatbot/">Thaura & WordPress: layout advice chatbot</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="1024" src="https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10.jpeg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" decoding="async" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10.jpeg 1024w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-300x300.jpeg 300w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-150x150.jpeg 150w, https://riceball.com/wp-content/uploads/sites/2/2026/09/57745157-b626-4bd6-b87d-186358473f10-768x768.jpeg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p><p class="wp-block-paragraph">This bot gives advice about how to make layouts in WordPress using the stock tools and features. More on this later, on to the Thaura Projects settings.</p>



<p class="wp-block-paragraph">Thaura Projects is a feature to allow you to create chatbots (and other interactive tools).</p>



<p class="wp-block-paragraph">To create a new Project, click on Project in the navigation, and then &#8220;+&#8221; in the upper right corner.</p>



<p class="wp-block-paragraph">Fill in the form with:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Title</td><td>WordPress Gutenberg Advice Bot</td></tr><tr><td>Description</td><td>A chatbot that advises how to produce what you want, using the features of the stock wordpress blocks, and the Site Editor features. If it seems difficult to do, then say so.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Then save it, and go to the Project. In the upper right, click the triple-dot menu and Edit.</p>



<p class="wp-block-paragraph">Add:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Project Instructions</td><td>If stuck, review the Core Blocks reference <a href="https://developer.wordpress.org/block-editor/reference-guides/core-blocks/" target="_blank" rel="noopener">https://developer.wordpress.org/block-editor/reference-guides/core-blocks/</a><br>If there&#8217;s no obvious solution read <a href="https://developer.wordpress.org/themes/" target="_blank" rel="noopener">https://developer.wordpress.org/themes/</a> for possible solutions.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">This is a screenshot of the dialog box where you paste in the prompts.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="461" height="561" src="https://riceball.com/wp-content/uploads/sites/2/2026/10/Screenshot-from-2026-10-08-21-08-40.png?wsr" alt="" class="wp-image-30726" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/10/Screenshot-from-2026-10-08-21-08-40.png 461w, https://riceball.com/wp-content/uploads/sites/2/2026/10/Screenshot-from-2026-10-08-21-08-40-247x300.png 247w" sizes="(max-width: 461px) 100vw, 461px" /></figure><p>The post <a href="https://riceball.com/2026/10/08/thaura-wordpress-layout-advice-chatbot/">Thaura & WordPress: layout advice chatbot</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://riceball.com/2026/10/08/thaura-wordpress-layout-advice-chatbot/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Thaura Work: time clock using .zsh_history</title>
		<link>https://riceball.com/2026/08/24/thaura-work-time-clock-using-zsh_history/</link>
					<comments>https://riceball.com/2026/08/24/thaura-work-time-clock-using-zsh_history/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Mon, 24 Aug 2026 21:15:52 +0000</pubDate>
				<category><![CDATA[Machine Learning]]></category>
		<category><![CDATA[Prompts]]></category>
		<category><![CDATA[Thaura]]></category>
		<guid isPermaLink="false">https://riceball.com/?p=30309</guid>

					<description><![CDATA[<p>A command line tool that uses the command history to implement a &#8220;punch clock&#8221; to record time worked. I use three kinds of timesheets &#8211; paper, Toggl, and shell history. I&#8217;ve always used a combination of tools, because, for example, I forget to click the &#8220;off&#8221; button on Toggle. Preparing accurate timesheets is sometimes a [&#8230;]</p>
<p>The post <a href="https://riceball.com/2026/08/24/thaura-work-time-clock-using-zsh_history/">Thaura Work: time clock using .zsh_history</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></description>
										<content:encoded><![CDATA[<p class="wp-block-paragraph">A command line tool that uses the command history to implement a &#8220;punch clock&#8221; to record time worked.</p>



<p class="wp-block-paragraph">I use three kinds of timesheets &#8211; paper, Toggl, and shell history. I&#8217;ve always used a combination of tools, because, for example, I forget to click the &#8220;off&#8221; button on Toggle. Preparing accurate timesheets is sometimes a process of finding all this history, and identifying the work intervals.</p>



<p class="wp-block-paragraph">I added to this collection of methods by creating a &#8220;clock in&#8221; command with this prompt:</p>



<p class="wp-block-paragraph"><em>create a CLI command, &#8220;clock&#8221;, that helps to keep track of time worked. it works in tandem with the .zsh_history, which is the source of truth, because running the program will show up in the history. when user types &#8220;clock in&#8221;, the program checks .zsh_history, and reports the last work interval. work intervals are the time periods between &#8220;clock in&#8221; and &#8220;clock out&#8221; commands. when the user types &#8220;clock out&#8221;, it reports the length of the work interval that has now terminated. when the user types &#8220;clock&#8221; without an argument, it reports all the work intervals for the last 35 days.</em></p>



<p class="wp-block-paragraph">The &#8220;clock&#8221; command produces reports that look like this:</p>


<pre class="wp-block-code"><span><code class="hljs language-css">➜  <span class="hljs-selector-tag">bin</span> <span class="hljs-selector-tag">clock</span>
<span class="hljs-selector-tag">work</span> <span class="hljs-selector-tag">intervals</span>, <span class="hljs-selector-tag">last</span> 35 <span class="hljs-selector-tag">days</span>
<span class="hljs-selector-tag">--------------------------------------------------------------------------</span>
  1. 2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:48</span><span class="hljs-selector-pseudo">:10</span>  <span class="hljs-selector-tag">-</span>&gt;  2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:48</span><span class="hljs-selector-pseudo">:18</span>   <span class="hljs-selector-attr">&#91;8s]</span>
  2. 2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:49</span><span class="hljs-selector-pseudo">:34</span>  <span class="hljs-selector-tag">-</span>&gt;  2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:50</span><span class="hljs-selector-pseudo">:46</span>   <span class="hljs-selector-attr">&#91;1m 12s]</span>
  3. 2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:50</span><span class="hljs-selector-pseudo">:56</span>  <span class="hljs-selector-tag">-</span>&gt;  2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:51</span><span class="hljs-selector-pseudo">:02</span>   <span class="hljs-selector-attr">&#91;6s]</span>
  4. 2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:51</span><span class="hljs-selector-pseudo">:29</span>  <span class="hljs-selector-tag">-</span>&gt;  2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:51</span><span class="hljs-selector-pseudo">:44</span>   <span class="hljs-selector-attr">&#91;15s]</span>
<span class="hljs-selector-tag">--------------------------------------------------------------------------</span>
<span class="hljs-selector-tag">total</span>: 4 <span class="hljs-selector-tag">interval</span>(<span class="hljs-selector-tag">s</span>), 1<span class="hljs-selector-tag">m</span> 41<span class="hljs-selector-tag">s</span> <span class="hljs-selector-tag">worked</span>

<span class="hljs-selector-tag">currently</span> <span class="hljs-selector-tag">CLOCKED</span> <span class="hljs-selector-tag">IN</span> <span class="hljs-selector-tag">since</span> 2026<span class="hljs-selector-tag">-08-24</span> 13<span class="hljs-selector-pseudo">:53</span><span class="hljs-selector-pseudo">:24</span> (16<span class="hljs-selector-tag">m</span> 02<span class="hljs-selector-tag">s</span> <span class="hljs-selector-tag">so</span> <span class="hljs-selector-tag">far</span>)
</code></span></pre>


<p class="wp-block-paragraph">Here&#8217;s the command it produced</p>



<div class="wp-block-file"><a id="wp-block-file--media-ebafcacc-8407-49ad-a537-8e0937e20f29" href="https://riceball.com/wp-content/uploads/sites/2/2026/08/clock.zip">clock</a><a href="https://riceball.com/wp-content/uploads/sites/2/2026/08/clock.zip" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-ebafcacc-8407-49ad-a537-8e0937e20f29"></a></div>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#24292e;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>#!/usr/bin/env python3
"""clock — track work intervals using $ZDOTDIR/.zsh_history as the source of truth.

Usage:
  clock in     Record a punch-in (the command lands in history automatically).
               Reports the last completed work interval for context.
  clock out    Close the open interval started by the most recent "clock in".
               Reports its length.
  clock        Report all work intervals from the last 35 days, plus totals.
"""

import os
import re
import sys
from datetime import datetime, timedelta

HISTORY_FILE = os.path.expanduser(os.environ.get("HISTFILE", "~/.zsh_history"))
WINDOW_DAYS = 35

# Matches zsh extended-history lines: ": &lt;epoch>:&lt;duration>;command"
LINE_RE = re.compile(r"^: (\d+):\d+;(.*)$")


LOCAL_TZ = datetime.now().astimezone().tzinfo


def dt(ts):
    return datetime.fromtimestamp(ts, tz=LOCAL_TZ)


def parse_history(path):
    """Return &#91;(timestamp, command)&#93; parsed from the history file."""
    entries = []
    try:
        with open(path, encoding="utf-8", errors="replace") as fh:
            for raw in fh:
                m = LINE_RE.match(raw.rstrip("\n"))
                if m:
                    ts = int(m.group(1))
                    cmd = m.group(2).strip()
                    # Skip continuation-line artifacts that carry no timestamp.
                    entries.append((ts, cmd))
    except FileNotFoundError:
        print(f"error: history file not found: {path}", file=sys.stderr)
        sys.exit(1)
    return entries


def is_clock(cmd, verb=None):
    """True if this history entry is a `clock &#91;verb&#93;` invocation."""
    parts = cmd.split(None, 1)
    if not parts or parts&#91;0&#93; != "clock":
        return False
    rest = parts&#91;1&#93;.strip() if len(parts) > 1 else ""
    if verb is None:
        return rest == ""
    return rest == verb


def find_punches(entries):
    """Pair clock punches from history into work intervals.

    Returns (intervals, open_start) where intervals is a list of
    (start_dt, end_dt) tuples oldest-first, and open_start is the
    start time of an unclosed interval, or None.
    """
    events = []
    for ts, c in entries:
        verb = None
        if is_clock(c, "in"):
            verb = "in"
        elif is_clock(c, "out"):
            verb = "out"
        if verb is not None:
            events.append((ts, verb))
    events.sort(key=lambda ev: ev&#91;0&#93;)

    # Pair punches left-to-right: each "in" opens an interval, closed by the
    # next "out". A stray "out" with nothing open is ignored; a stray "in"
    # leaves the interval open.
    #
    # Note on the current invocation: zsh writes the running command's own
    # history entry at varying times depending on how the shell was started,
    # so we deliberately do NOT special-case it. For `clock in` the stale
    # state is harmless (it just prints "already clocked in" once). For
    # `clock out`, if the matching `clock in` hasn't been flushed yet, the
    # close fails cleanly — run `clock out` again a moment later.
    intervals = []
    open_start = None
    for ts, verb in events:
        if verb == "in":
            open_start = dt(ts)
        else:
            if open_start is not None:
                intervals.append((open_start, dt(ts)))
                open_start = None
    return intervals, open_start


def fmt_duration(seconds):
    seconds = int(round(seconds))
    h, rem = divmod(abs(seconds), 3600)
    m, s = divmod(rem, 60)
    sign = "-" if seconds &lt; 0 else ""
    if h:
        return f"{sign}{h}h {m:02d}m {s:02d}s"
    if m:
        return f"{sign}{m}m {s:02d}s"
    return f"{sign}{s}s"


def fmt_stamp(dt):
    return dt.strftime("%Y-%m-%d %H:%M:%S")


def report_intervals(intervals, title):
    total = sum((e - s).total_seconds() for s, e in intervals)
    print(title)
    print("-" * 74)
    if not intervals:
        print("(no complete work intervals)")
    for idx, (s, e) in enumerate(intervals, 1):
        dur = (e - s).total_seconds()
        print(f"{idx:>3}. {fmt_stamp(s)}  ->  {fmt_stamp(e)}   &#91;{fmt_duration(dur)}&#93;")
    if intervals:
        print("-" * 74)
        print(f"total: {len(intervals)} interval(s), {fmt_duration(total)} worked")


def main():
    args = sys.argv&#91;1:&#93;
    if len(args) > 1 or args and args&#91;0&#93; not in ("in", "out"):
        print(__doc__.strip(), file=sys.stderr)
        sys.exit(2)

    now = datetime.now().astimezone()
    entries = parse_history(HISTORY_FILE)
    intervals, open_start = find_punches(entries)

    if not args:
        since = now - timedelta(days=WINDOW_DAYS)
        recent = &#91;(s, e) for s, e in intervals if s >= since&#93;
        report_intervals(recent, f"work intervals, last {WINDOW_DAYS} days")
        if open_start:
            elapsed = (now - open_start).total_seconds()
            print(f"\ncurrently CLOCKED IN since {fmt_stamp(open_start)} ({fmt_duration(elapsed)} so far)")
        elif recent:
            print("\nstatus: clocked out")
        return

    if args&#91;0&#93; == "in":
        if open_start:
            print(f"already clocked in since {fmt_stamp(open_start)} "
                  f"({fmt_duration((now - open_start).total_seconds())} so far)")
            print("use 'clock out' first if you meant to close that interval.")
            sys.exit(1)
        print(f"clocked in at {fmt_stamp(now)}")
        if intervals:
            ls, le = intervals&#91;-1&#93;
            print(f"last interval: {fmt_stamp(ls)} -> {fmt_stamp(le)} "
                  f"&#91;{fmt_duration((le - ls).total_seconds())}&#93;")
        return

    # clock out
    if not open_start:
        print("not clocked in — nothing to close.", file=sys.stderr)
        sys.exit(1)
    closed = (now - open_start).total_seconds()
    print(f"interval closed: {fmt_stamp(open_start)} -> {fmt_stamp(now)} "
          f"&#91;{fmt_duration(closed)}&#93;")


if __name__ == "__main__":
    main()
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-light" style="background-color: #fff" tabindex="0"><code><span class="line"><span style="color: #6A737D">#!/usr/bin/env python3</span></span>
<span class="line"><span style="color: #032F62">&quot;&quot;&quot;clock — track work intervals using $ZDOTDIR/.zsh_history as the source of truth.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #032F62">Usage:</span></span>
<span class="line"><span style="color: #032F62">  clock in     Record a punch-in (the command lands in history automatically).</span></span>
<span class="line"><span style="color: #032F62">               Reports the last completed work interval for context.</span></span>
<span class="line"><span style="color: #032F62">  clock out    Close the open interval started by the most recent &quot;clock in&quot;.</span></span>
<span class="line"><span style="color: #032F62">               Reports its length.</span></span>
<span class="line"><span style="color: #032F62">  clock        Report all work intervals from the last 35 days, plus totals.</span></span>
<span class="line"><span style="color: #032F62">&quot;&quot;&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> os</span></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> re</span></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> sys</span></span>
<span class="line"><span style="color: #D73A49">from</span><span style="color: #24292E"> datetime </span><span style="color: #D73A49">import</span><span style="color: #24292E"> datetime, timedelta</span></span>
<span class="line"></span>
<span class="line"><span style="color: #005CC5">HISTORY_FILE</span><span style="color: #24292E"> </span><span style="color: #D73A49">=</span><span style="color: #24292E"> os.path.expanduser(os.environ.get(</span><span style="color: #032F62">&quot;HISTFILE&quot;</span><span style="color: #24292E">, </span><span style="color: #032F62">&quot;~/.zsh_history&quot;</span><span style="color: #24292E">))</span></span>
<span class="line"><span style="color: #005CC5">WINDOW_DAYS</span><span style="color: #24292E"> </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">35</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6A737D"># Matches zsh extended-history lines: &quot;: &lt;epoch&gt;:&lt;duration&gt;;command&quot;</span></span>
<span class="line"><span style="color: #005CC5">LINE_RE</span><span style="color: #24292E"> </span><span style="color: #D73A49">=</span><span style="color: #24292E"> re.compile(</span><span style="color: #D73A49">r</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">^</span><span style="color: #032F62">: </span><span style="color: #005CC5">(\d</span><span style="color: #D73A49">+</span><span style="color: #005CC5">)</span><span style="color: #032F62">:</span><span style="color: #005CC5">\d</span><span style="color: #D73A49">+</span><span style="color: #032F62">;</span><span style="color: #005CC5">(.</span><span style="color: #D73A49">*</span><span style="color: #005CC5">)$</span><span style="color: #032F62">&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #005CC5">LOCAL_TZ</span><span style="color: #24292E"> </span><span style="color: #D73A49">=</span><span style="color: #24292E"> datetime.now().astimezone().tzinfo</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">dt</span><span style="color: #24292E">(ts):</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> datetime.fromtimestamp(ts, </span><span style="color: #E36209">tz</span><span style="color: #D73A49">=</span><span style="color: #005CC5">LOCAL_TZ</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">parse_history</span><span style="color: #24292E">(path):</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #032F62">&quot;&quot;&quot;Return &#91;(timestamp, command)&#93; parsed from the history file.&quot;&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    entries </span><span style="color: #D73A49">=</span><span style="color: #24292E"> []</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">try</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">with</span><span style="color: #24292E"> </span><span style="color: #005CC5">open</span><span style="color: #24292E">(path, </span><span style="color: #E36209">encoding</span><span style="color: #D73A49">=</span><span style="color: #032F62">&quot;utf-8&quot;</span><span style="color: #24292E">, </span><span style="color: #E36209">errors</span><span style="color: #D73A49">=</span><span style="color: #032F62">&quot;replace&quot;</span><span style="color: #24292E">) </span><span style="color: #D73A49">as</span><span style="color: #24292E"> fh:</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #D73A49">for</span><span style="color: #24292E"> raw </span><span style="color: #D73A49">in</span><span style="color: #24292E"> fh:</span></span>
<span class="line"><span style="color: #24292E">                m </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">LINE_RE</span><span style="color: #24292E">.match(raw.rstrip(</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">\n</span><span style="color: #032F62">&quot;</span><span style="color: #24292E">))</span></span>
<span class="line"><span style="color: #24292E">                </span><span style="color: #D73A49">if</span><span style="color: #24292E"> m:</span></span>
<span class="line"><span style="color: #24292E">                    ts </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">int</span><span style="color: #24292E">(m.group(</span><span style="color: #005CC5">1</span><span style="color: #24292E">))</span></span>
<span class="line"><span style="color: #24292E">                    cmd </span><span style="color: #D73A49">=</span><span style="color: #24292E"> m.group(</span><span style="color: #005CC5">2</span><span style="color: #24292E">).strip()</span></span>
<span class="line"><span style="color: #24292E">                    </span><span style="color: #6A737D"># Skip continuation-line artifacts that carry no timestamp.</span></span>
<span class="line"><span style="color: #24292E">                    entries.append((ts, cmd))</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">except</span><span style="color: #24292E"> </span><span style="color: #005CC5">FileNotFoundError</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;error: history file not found: </span><span style="color: #005CC5">{</span><span style="color: #24292E">path</span><span style="color: #005CC5">}</span><span style="color: #032F62">&quot;</span><span style="color: #24292E">, </span><span style="color: #E36209">file</span><span style="color: #D73A49">=</span><span style="color: #24292E">sys.stderr)</span></span>
<span class="line"><span style="color: #24292E">        sys.exit(</span><span style="color: #005CC5">1</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> entries</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">is_clock</span><span style="color: #24292E">(cmd, verb</span><span style="color: #D73A49">=</span><span style="color: #005CC5">None</span><span style="color: #24292E">):</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #032F62">&quot;&quot;&quot;True if this history entry is a `clock &#91;verb&#93;` invocation.&quot;&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    parts </span><span style="color: #D73A49">=</span><span style="color: #24292E"> cmd.split(</span><span style="color: #005CC5">None</span><span style="color: #24292E">, </span><span style="color: #005CC5">1</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> parts </span><span style="color: #D73A49">or</span><span style="color: #24292E"> parts&#91;</span><span style="color: #005CC5">0</span><span style="color: #24292E">&#93; </span><span style="color: #D73A49">!=</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;clock&quot;</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span><span style="color: #24292E"> </span><span style="color: #005CC5">False</span></span>
<span class="line"><span style="color: #24292E">    rest </span><span style="color: #D73A49">=</span><span style="color: #24292E"> parts&#91;</span><span style="color: #005CC5">1</span><span style="color: #24292E">&#93;.strip() </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #005CC5">len</span><span style="color: #24292E">(parts) </span><span style="color: #D73A49">&gt;</span><span style="color: #24292E"> </span><span style="color: #005CC5">1</span><span style="color: #24292E"> </span><span style="color: #D73A49">else</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> verb </span><span style="color: #D73A49">is</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span><span style="color: #24292E"> rest </span><span style="color: #D73A49">==</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> rest </span><span style="color: #D73A49">==</span><span style="color: #24292E"> verb</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">find_punches</span><span style="color: #24292E">(entries):</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #032F62">&quot;&quot;&quot;Pair clock punches from history into work intervals.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #032F62">    Returns (intervals, open_start) where intervals is a list of</span></span>
<span class="line"><span style="color: #032F62">    (start_dt, end_dt) tuples oldest-first, and open_start is the</span></span>
<span class="line"><span style="color: #032F62">    start time of an unclosed interval, or None.</span></span>
<span class="line"><span style="color: #032F62">    &quot;&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    events </span><span style="color: #D73A49">=</span><span style="color: #24292E"> []</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">for</span><span style="color: #24292E"> ts, c </span><span style="color: #D73A49">in</span><span style="color: #24292E"> entries:</span></span>
<span class="line"><span style="color: #24292E">        verb </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> is_clock(c, </span><span style="color: #032F62">&quot;in&quot;</span><span style="color: #24292E">):</span></span>
<span class="line"><span style="color: #24292E">            verb </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;in&quot;</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">elif</span><span style="color: #24292E"> is_clock(c, </span><span style="color: #032F62">&quot;out&quot;</span><span style="color: #24292E">):</span></span>
<span class="line"><span style="color: #24292E">            verb </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;out&quot;</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> verb </span><span style="color: #D73A49">is</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">            events.append((ts, verb))</span></span>
<span class="line"><span style="color: #24292E">    events.sort(</span><span style="color: #E36209">key</span><span style="color: #D73A49">=lambda</span><span style="color: #24292E"> ev: ev&#91;</span><span style="color: #005CC5">0</span><span style="color: #24292E">&#93;)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># Pair punches left-to-right: each &quot;in&quot; opens an interval, closed by the</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># next &quot;out&quot;. A stray &quot;out&quot; with nothing open is ignored; a stray &quot;in&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># leaves the interval open.</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D">#</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># Note on the current invocation: zsh writes the running command&#39;s own</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># history entry at varying times depending on how the shell was started,</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># so we deliberately do NOT special-case it. For `clock in` the stale</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># state is harmless (it just prints &quot;already clocked in&quot; once). For</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># `clock out`, if the matching `clock in` hasn&#39;t been flushed yet, the</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># close fails cleanly — run `clock out` again a moment later.</span></span>
<span class="line"><span style="color: #24292E">    intervals </span><span style="color: #D73A49">=</span><span style="color: #24292E"> []</span></span>
<span class="line"><span style="color: #24292E">    open_start </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">for</span><span style="color: #24292E"> ts, verb </span><span style="color: #D73A49">in</span><span style="color: #24292E"> events:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> verb </span><span style="color: #D73A49">==</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;in&quot;</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">            open_start </span><span style="color: #D73A49">=</span><span style="color: #24292E"> dt(ts)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">else</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #D73A49">if</span><span style="color: #24292E"> open_start </span><span style="color: #D73A49">is</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">                intervals.append((open_start, dt(ts)))</span></span>
<span class="line"><span style="color: #24292E">                open_start </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">None</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> intervals, open_start</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">fmt_duration</span><span style="color: #24292E">(seconds):</span></span>
<span class="line"><span style="color: #24292E">    seconds </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">int</span><span style="color: #24292E">(</span><span style="color: #005CC5">round</span><span style="color: #24292E">(seconds))</span></span>
<span class="line"><span style="color: #24292E">    h, rem </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">divmod</span><span style="color: #24292E">(</span><span style="color: #005CC5">abs</span><span style="color: #24292E">(seconds), </span><span style="color: #005CC5">3600</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    m, s </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">divmod</span><span style="color: #24292E">(rem, </span><span style="color: #005CC5">60</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    sign </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;-&quot;</span><span style="color: #24292E"> </span><span style="color: #D73A49">if</span><span style="color: #24292E"> seconds </span><span style="color: #D73A49">&lt;</span><span style="color: #24292E"> </span><span style="color: #005CC5">0</span><span style="color: #24292E"> </span><span style="color: #D73A49">else</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> h:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span><span style="color: #24292E"> </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">{</span><span style="color: #24292E">sign</span><span style="color: #005CC5">}{</span><span style="color: #24292E">h</span><span style="color: #005CC5">}</span><span style="color: #032F62">h </span><span style="color: #005CC5">{</span><span style="color: #24292E">m</span><span style="color: #D73A49">:02d</span><span style="color: #005CC5">}</span><span style="color: #032F62">m </span><span style="color: #005CC5">{</span><span style="color: #24292E">s</span><span style="color: #D73A49">:02d</span><span style="color: #005CC5">}</span><span style="color: #032F62">s&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> m:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span><span style="color: #24292E"> </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">{</span><span style="color: #24292E">sign</span><span style="color: #005CC5">}{</span><span style="color: #24292E">m</span><span style="color: #005CC5">}</span><span style="color: #032F62">m </span><span style="color: #005CC5">{</span><span style="color: #24292E">s</span><span style="color: #D73A49">:02d</span><span style="color: #005CC5">}</span><span style="color: #032F62">s&quot;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">{</span><span style="color: #24292E">sign</span><span style="color: #005CC5">}{</span><span style="color: #24292E">s</span><span style="color: #005CC5">}</span><span style="color: #032F62">s&quot;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">fmt_stamp</span><span style="color: #24292E">(dt):</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">return</span><span style="color: #24292E"> dt.strftime(</span><span style="color: #032F62">&quot;%Y-%m-</span><span style="color: #005CC5">%d</span><span style="color: #032F62"> %H:%M:%S&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">report_intervals</span><span style="color: #24292E">(intervals, title):</span></span>
<span class="line"><span style="color: #24292E">    total </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">sum</span><span style="color: #24292E">((e </span><span style="color: #D73A49">-</span><span style="color: #24292E"> s).total_seconds() </span><span style="color: #D73A49">for</span><span style="color: #24292E"> s, e </span><span style="color: #D73A49">in</span><span style="color: #24292E"> intervals)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #005CC5">print</span><span style="color: #24292E">(title)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;-&quot;</span><span style="color: #24292E"> </span><span style="color: #D73A49">*</span><span style="color: #24292E"> </span><span style="color: #005CC5">74</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> intervals:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;(no complete work intervals)&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">for</span><span style="color: #24292E"> idx, (s, e) </span><span style="color: #D73A49">in</span><span style="color: #24292E"> </span><span style="color: #005CC5">enumerate</span><span style="color: #24292E">(intervals, </span><span style="color: #005CC5">1</span><span style="color: #24292E">):</span></span>
<span class="line"><span style="color: #24292E">        dur </span><span style="color: #D73A49">=</span><span style="color: #24292E"> (e </span><span style="color: #D73A49">-</span><span style="color: #24292E"> s).total_seconds()</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">{</span><span style="color: #24292E">idx</span><span style="color: #D73A49">:&gt;3</span><span style="color: #005CC5">}</span><span style="color: #032F62">. </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(s)</span><span style="color: #005CC5">}</span><span style="color: #032F62">  -&gt;  </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(e)</span><span style="color: #005CC5">}</span><span style="color: #032F62">   &#91;</span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration(dur)</span><span style="color: #005CC5">}</span><span style="color: #032F62">&#93;&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> intervals:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;-&quot;</span><span style="color: #24292E"> </span><span style="color: #D73A49">*</span><span style="color: #24292E"> </span><span style="color: #005CC5">74</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;total: </span><span style="color: #005CC5">{len</span><span style="color: #24292E">(intervals)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> interval(s), </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration(total)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> worked&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">def</span><span style="color: #24292E"> </span><span style="color: #6F42C1">main</span><span style="color: #24292E">():</span></span>
<span class="line"><span style="color: #24292E">    args </span><span style="color: #D73A49">=</span><span style="color: #24292E"> sys.argv&#91;</span><span style="color: #005CC5">1</span><span style="color: #24292E">:&#93;</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #005CC5">len</span><span style="color: #24292E">(args) </span><span style="color: #D73A49">&gt;</span><span style="color: #24292E"> </span><span style="color: #005CC5">1</span><span style="color: #24292E"> </span><span style="color: #D73A49">or</span><span style="color: #24292E"> args </span><span style="color: #D73A49">and</span><span style="color: #24292E"> args&#91;</span><span style="color: #005CC5">0</span><span style="color: #24292E">&#93; </span><span style="color: #D73A49">not</span><span style="color: #24292E"> </span><span style="color: #D73A49">in</span><span style="color: #24292E"> (</span><span style="color: #032F62">&quot;in&quot;</span><span style="color: #24292E">, </span><span style="color: #032F62">&quot;out&quot;</span><span style="color: #24292E">):</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #005CC5">__doc__</span><span style="color: #24292E">.strip(), </span><span style="color: #E36209">file</span><span style="color: #D73A49">=</span><span style="color: #24292E">sys.stderr)</span></span>
<span class="line"><span style="color: #24292E">        sys.exit(</span><span style="color: #005CC5">2</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #24292E">    now </span><span style="color: #D73A49">=</span><span style="color: #24292E"> datetime.now().astimezone()</span></span>
<span class="line"><span style="color: #24292E">    entries </span><span style="color: #D73A49">=</span><span style="color: #24292E"> parse_history(</span><span style="color: #005CC5">HISTORY_FILE</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    intervals, open_start </span><span style="color: #D73A49">=</span><span style="color: #24292E"> find_punches(entries)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> args:</span></span>
<span class="line"><span style="color: #24292E">        since </span><span style="color: #D73A49">=</span><span style="color: #24292E"> now </span><span style="color: #D73A49">-</span><span style="color: #24292E"> timedelta(</span><span style="color: #E36209">days</span><span style="color: #D73A49">=</span><span style="color: #005CC5">WINDOW_DAYS</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        recent </span><span style="color: #D73A49">=</span><span style="color: #24292E"> &#91;(s, e) </span><span style="color: #D73A49">for</span><span style="color: #24292E"> s, e </span><span style="color: #D73A49">in</span><span style="color: #24292E"> intervals </span><span style="color: #D73A49">if</span><span style="color: #24292E"> s </span><span style="color: #D73A49">&gt;=</span><span style="color: #24292E"> since&#93;</span></span>
<span class="line"><span style="color: #24292E">        report_intervals(recent, </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;work intervals, last </span><span style="color: #005CC5">{WINDOW_DAYS}</span><span style="color: #032F62"> days&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> open_start:</span></span>
<span class="line"><span style="color: #24292E">            elapsed </span><span style="color: #D73A49">=</span><span style="color: #24292E"> (now </span><span style="color: #D73A49">-</span><span style="color: #24292E"> open_start).total_seconds()</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">\n</span><span style="color: #032F62">currently CLOCKED IN since </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(open_start)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> (</span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration(elapsed)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> so far)&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">elif</span><span style="color: #24292E"> recent:</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;</span><span style="color: #005CC5">\n</span><span style="color: #032F62">status: clocked out&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span></span>
<span class="line"></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> args&#91;</span><span style="color: #005CC5">0</span><span style="color: #24292E">&#93; </span><span style="color: #D73A49">==</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;in&quot;</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> open_start:</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;already clocked in since </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(open_start)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> &quot;</span></span>
<span class="line"><span style="color: #24292E">                  </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;(</span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration((now </span><span style="color: #D73A49">-</span><span style="color: #24292E"> open_start).total_seconds())</span><span style="color: #005CC5">}</span><span style="color: #032F62"> so far)&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;use &#39;clock out&#39; first if you meant to close that interval.&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">            sys.exit(</span><span style="color: #005CC5">1</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;clocked in at </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(now)</span><span style="color: #005CC5">}</span><span style="color: #032F62">&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">if</span><span style="color: #24292E"> intervals:</span></span>
<span class="line"><span style="color: #24292E">            ls, le </span><span style="color: #D73A49">=</span><span style="color: #24292E"> intervals&#91;</span><span style="color: #D73A49">-</span><span style="color: #005CC5">1</span><span style="color: #24292E">&#93;</span></span>
<span class="line"><span style="color: #24292E">            </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;last interval: </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(ls)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> -&gt; </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(le)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> &quot;</span></span>
<span class="line"><span style="color: #24292E">                  </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;&#91;</span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration((le </span><span style="color: #D73A49">-</span><span style="color: #24292E"> ls).total_seconds())</span><span style="color: #005CC5">}</span><span style="color: #032F62">&#93;&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #D73A49">return</span></span>
<span class="line"></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #6A737D"># clock out</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #D73A49">not</span><span style="color: #24292E"> open_start:</span></span>
<span class="line"><span style="color: #24292E">        </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #032F62">&quot;not clocked in — nothing to close.&quot;</span><span style="color: #24292E">, </span><span style="color: #E36209">file</span><span style="color: #D73A49">=</span><span style="color: #24292E">sys.stderr)</span></span>
<span class="line"><span style="color: #24292E">        sys.exit(</span><span style="color: #005CC5">1</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    closed </span><span style="color: #D73A49">=</span><span style="color: #24292E"> (now </span><span style="color: #D73A49">-</span><span style="color: #24292E"> open_start).total_seconds()</span></span>
<span class="line"><span style="color: #24292E">    </span><span style="color: #005CC5">print</span><span style="color: #24292E">(</span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;interval closed: </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(open_start)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> -&gt; </span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_stamp(now)</span><span style="color: #005CC5">}</span><span style="color: #032F62"> &quot;</span></span>
<span class="line"><span style="color: #24292E">          </span><span style="color: #D73A49">f</span><span style="color: #032F62">&quot;&#91;</span><span style="color: #005CC5">{</span><span style="color: #24292E">fmt_duration(closed)</span><span style="color: #005CC5">}</span><span style="color: #032F62">&#93;&quot;</span><span style="color: #24292E">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">if</span><span style="color: #24292E"> </span><span style="color: #005CC5">__name__</span><span style="color: #24292E"> </span><span style="color: #D73A49">==</span><span style="color: #24292E"> </span><span style="color: #032F62">&quot;__main__&quot;</span><span style="color: #24292E">:</span></span>
<span class="line"><span style="color: #24292E">    main()</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">That single prompt produced the entire program, without any intervention on my part. (Getting to that prompt required a little research into .zsh_history logging, though.1) Watching it write the code was fascinating. It was testing use cases, testing how Zsh records history, adding useful messages.</p>



<p class="wp-block-paragraph">I&#8217;ve only skimmed the code, and can&#8217;t say how good it is.</p>



<p class="wp-block-paragraph">I wouldn&#8217;t have written it this way. I would have used grep.</p>



<h3 class="wp-block-heading">Note</h3>



<p class="wp-block-paragraph">1. A long time ago, I created a text-based timesheet calculator. Then, I created a program called log_everything that would run on all my computers, and compile log files from command history, browser history, and other data sources. Writing a log importer for any existing log file was trivial. I think I was scanning mailbox files, too. I could get a comprehensive overview of personal activities from a single database. It was useful for seeing what work I&#8217;d done, but, it was creepy to spy on myself.</p>



<p class="wp-block-paragraph"></p><p>The post <a href="https://riceball.com/2026/08/24/thaura-work-time-clock-using-zsh_history/">Thaura Work: time clock using .zsh_history</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://riceball.com/2026/08/24/thaura-work-time-clock-using-zsh_history/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Thaura Work: Vibe Coding an RSS Feed Aggregator</title>
		<link>https://riceball.com/2026/06/26/thaura-work-vibe-coding-an-rss-feed-aggregator/</link>
					<comments>https://riceball.com/2026/06/26/thaura-work-vibe-coding-an-rss-feed-aggregator/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 09:29:41 +0000</pubDate>
				<category><![CDATA[Machine Learning]]></category>
		<category><![CDATA[Obsidian]]></category>
		<category><![CDATA[Prompts]]></category>
		<category><![CDATA[Thaura]]></category>
		<guid isPermaLink="false">https://riceball.com/?p=29751</guid>

					<description><![CDATA[<p>My feed aggregator finds RSS URLs, and converts feeds into Markdown files with headlines and links. A CLI orchestrates the processing, which is done by commands. A simple GUI helps the user accumulate URLs. Obsidian is used to browse the resulting folder structure. This document describes some of the architecture and some design decisions, and [&#8230;]</p>
<p>The post <a href="https://riceball.com/2026/06/26/thaura-work-vibe-coding-an-rss-feed-aggregator/">Thaura Work: Vibe Coding an RSS Feed Aggregator</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></description>
										<content:encoded><![CDATA[<p class="wp-block-paragraph">My feed aggregator finds RSS URLs, and converts feeds into Markdown files with headlines and links. A CLI orchestrates the processing, which is done by commands. A simple GUI helps the user accumulate URLs. Obsidian is used to browse the resulting folder structure.</p>



<p class="wp-block-paragraph"><em>This document describes some of the architecture and some design decisions, and some prompts.</em></p>



<p class="wp-block-paragraph">Thaura Work is a feature in the Thaura desktop app that allows the LLM to access your files. It&#8217;s a shell around OpenCode.</p>



<p class="wp-block-paragraph">Here are some screenshots. First, the &#8220;user&#8221; side:</p>



<figure class="wp-block-image size-large"><a href="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59.png"><img loading="lazy" decoding="async" width="1024" height="576" src="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-1024x576.png" alt="" class="wp-image-29752" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-1024x576.png 1024w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-300x169.png 300w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-768x432.png 768w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-1536x864.png 1536w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<p class="wp-block-paragraph">(I&#8217;m using the Regolith tiling window manager.) The leftmost pane is the web browser. The middle pane is Obsidian, displaying headlines from an RSS feed. The right pane is the graphical user interface that lets me put a URL into the system.</p>



<p class="wp-block-paragraph"><strong>The most general use case</strong> is to find a web site or page, and copypaste the URL into the GUI, which then runs the program. If there&#8217;s an RSS feed, it&#8217;s downloaded, and if there are recent stories, they&#8217;re written out as a Markdown file.</p>



<p class="wp-block-paragraph">Here is the &#8220;programmer&#8221; screen:</p>



<figure class="wp-block-image size-large"><a href="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09.png"><img loading="lazy" decoding="async" width="1024" height="576" src="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09-1024x576.png" alt="" class="wp-image-29754" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09-1024x576.png 1024w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09-300x169.png 300w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09-768x432.png 768w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09-1536x864.png 1536w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-11-09.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<p class="wp-block-paragraph">The left page shows the folder structure and programs &#8212; one is a double clickable icon that will launch the user interface. The middle pane shows the output from running run.py, the master command. The right pane shows Thaura Work.</p>



<p class="wp-block-paragraph">Normally, I use tabs instead of panes. (The window manager is Regolith.)</p>



<p class="wp-block-paragraph">The most common use cases here are:</p>



<ul class="wp-block-list">
<li>Running <code>run.py</code> bare, to process the inbox.</li>



<li>Running <code>run.py --refresh</code>, to process known good URLs.</li>



<li>Telling Thaura Work to write or debug code.</li>



<li>Looking for program misbehavior in the folders.</li>
</ul>



<h2 class="wp-block-heading">Design Decisions</h2>



<p class="wp-block-paragraph">Thaura Work seemed to be choking on long source code, so <strong>I decided to build the system up as Python commands</strong>. To grow the program, I can add more commands, and call them from a &#8220;master control&#8221; program called run.py.</p>



<p class="wp-block-paragraph">The program is basically a batch processor, which I&#8217;ll call a &#8220;workflow&#8221;. </p>



<p class="wp-block-paragraph">Users provide URLs in text files, stdin, and command line args.</p>



<p class="wp-block-paragraph"><strong>I decided NOT to use a database, but to use the file system like a database.</strong></p>



<p class="wp-block-paragraph">Data records are in small text files. Folders act as state buckets, and text files are moved (or read from and written) to folders.</p>



<p class="wp-block-paragraph">You have an &#8220;inbox&#8221; folder. The run.py command processes it.</p>



<p class="wp-block-paragraph">URL files get classified into &#8220;urls.bad&#8221; and &#8220;urls.good&#8221; by another command. </p>



<p class="wp-block-paragraph">The system checks RSS feeds with another command, and the ones that have no data or stale data get put into &#8220;rssfeed.bad&#8221;.</p>



<p class="wp-block-paragraph"><strong>Each stage in the process has its own command that writes data into these folders. That&#8217;s the workflow.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">The data files should look like they move through the folders organized like this:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="410" height="209" src="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-11-40-57.png" alt="" class="wp-image-29780" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-11-40-57.png 410w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-11-40-57-300x153.png 300w" sizes="auto, (max-width: 410px) 100vw, 410px" /></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Example file in rssfeed.bad:</p>



<pre class="wp-block-preformatted"># RSS URL: https://www.pcgamer.com/feeds.xml<br># Error: No recent articles found in past 24 hours<br># Moved to rssfeed.bad: 2026-06-26T02:17:59.706051</pre>



<p class="wp-block-paragraph">Example file in url.good:</p>



<p class="wp-block-paragraph">https://www.pcgamer.com https://www.pcgamer.com/feeds.xml</p>



<p class="wp-block-paragraph">There&#8217;s not really any rhyme or reason to these formats. They just evolved, so they&#8217;ll need to be forced to converge onto a single format.</p>



<p class="wp-block-paragraph">Initially, I imagined the URL files would just pass through these folders. What actually happened was, the data was transformed, so files were read and written into these folders.</p>



<p class="wp-block-paragraph"><strong>Filenames are standardized</strong> and look like this: c9a9d5ad_www_kotaku_com.txt</p>



<p class="wp-block-paragraph">That&#8217;s a hash of the URL and the domain name. This is useful for deduplication, and because some web pages have multiple RSS feeds on it.</p>



<p class="wp-block-paragraph"><strong>Rather than create HTML output, I decided to make Markdown output</strong>, because the Obsidian editor is smoother than using a web browser. (An HTML version of the project would require creating index files, at the very least.)</p>



<p class="wp-block-paragraph">I also wanted the program to be able to produce other formats in the future, like JSON files, so emitting Markdown seemed like a good first step. </p>



<p class="wp-block-paragraph"><strong>The decision was to have files as input, and files as output, so this workflow could be part of a larger workflow.</strong></p>



<p class="wp-block-paragraph">At all times, I wanted the programs to run standalone, outside of Thaura Work. Thaura Work can run the program, and I have it do that, but I would like to just run the workflow without Thaura Work most of the time.</p>



<p class="wp-block-paragraph"><strong>The decision was to run outside of Thaura, and save energy.</strong></p>



<h2 class="wp-block-heading">Other Decisions</h2>



<p class="wp-block-paragraph">I don&#8217;t really like the program&#8217;s output, and prefer a less chatty TTY, but Thaura Work uses the extensive messages as a debugging aid. So they remain.</p>



<p class="wp-block-paragraph">I didn&#8217;t like a lot of the code it produced, at first, but have fought resisted &#8220;fixing&#8221; it. I assume the code produced is a reverse map back into the conceptual tokens deep in the model. I shouldn&#8217;t touch it too much.</p>



<p class="wp-block-paragraph">I had it create a GUI program for the most common form of input &#8211; a single URL. I didn&#8217;t want to use Terminal to submit my URLs.</p>



<p class="wp-block-paragraph">It created a great little one-line input dialog, and it shows the program output in the lower pane. That&#8217;s so snazzy!</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="810" height="1080" src="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-edited.png" alt="" class="wp-image-29763" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-edited.png 810w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-edited-225x300.png 225w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-02-02-59-edited-768x1024.png 768w" sizes="auto, (max-width: 810px) 100vw, 810px" /></figure>



<p class="wp-block-paragraph">I had it write a C program to create a double clickable icon that runs the GUI program.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="609" height="666" src="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-03-07-46.png" alt="" class="wp-image-29762" srcset="https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-03-07-46.png 609w, https://riceball.com/wp-content/uploads/sites/2/2026/06/Screenshot-from-2026-06-26-03-07-46-274x300.png 274w" sizes="auto, (max-width: 609px) 100vw, 609px" /></figure>



<h2 class="wp-block-heading">Some Interesting Prompts</h2>


<pre class="wp-block-code has-d-8-dee-9-ff-color has-text-color has-875-rem-font-size"><span><code class="hljs language-javascript shcb-code-table shcb-wrap-lines"><mark class='shcb-loc'><span>run the command <span class="hljs-keyword">with</span> https:<span class="hljs-comment">//truthout.org observe the output and fix bugs</span>
</span></mark></code></span></pre>


<p class="wp-block-paragraph">It just ran the program over and over, fixing bugs. Pretty amazing.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-code-table shcb-wrap-lines"><mark class='shcb-loc'><span>make sure that when run.py is called <span class="hljs-keyword">with</span> --refresh, that it resets the state <span class="hljs-keyword">of</span> all the good urls <span class="hljs-keyword">from</span> processed to unprocessed
</span></mark></code></span></pre>


<p class="wp-block-paragraph">This created a way to re-run the good URLs, running a step in the workflow without running all the steps. The workflow is made up of steps, each one working a bit like a workflow.</p>


<pre class="wp-block-code"><span><code class="hljs language-php shcb-code-table shcb-wrap-lines"><mark class='shcb-loc'><span>create a naming convention <span class="hljs-keyword">for</span> the files in the url <span class="hljs-keyword">and</span> rssurl folders. <span class="hljs-keyword">use</span> <span class="hljs-title">a</span> <span class="hljs-title">hash</span>(<span class="hljs-title">url</span>) <span class="hljs-title">and</span> <span class="hljs-title">domain</span> <span class="hljs-title">in</span> <span class="hljs-title">the</span> <span class="hljs-title">filename</span>. <span class="hljs-title">this</span> <span class="hljs-title">can</span> <span class="hljs-title">help</span> <span class="hljs-title">speed</span> <span class="hljs-title">up</span> <span class="hljs-title">deduping</span> <span class="hljs-title">urls</span>.
</span></mark></code></span></pre>


<p class="wp-block-paragraph">This was really nice. It understands about putting the data in the file, and essential metadata in the filename, so it forms a &#8220;key&#8221;.</p>



<p class="wp-block-paragraph">After stating this, it edited the code to use the convention all over. Unfortunately, it seems to have repeated code. If I were coding this, I&#8217;d have a library of functions to create standardized names. Oh well.</p>



<p class="wp-block-paragraph">The code is only as correct as it has to be.</p>


<pre class="wp-block-code"><span><code class="hljs shcb-code-table shcb-wrap-lines"><mark class='shcb-loc'><span>please clean up the project, removing deprecated programs
</span></mark></code></span></pre>


<p class="wp-block-paragraph">It is doing a much hated task!</p>



<p class="wp-block-paragraph">(I shouldn&#8217;t have used &#8220;please&#8221;.)</p>



<h2 class="wp-block-heading">Surprises and Thoughts</h2>



<p class="wp-block-paragraph">I had originally made the program to process a single URL at a time. Along the way, Thaura Work subverted me, and changed it, so processing the inbox loops over the whole thing.</p>



<p class="wp-block-paragraph">I did not want that optimization, because it means I can&#8217;t interrupt the workflow, quit after the current &#8220;crank of the handle&#8221;, and resume it.</p>



<p class="wp-block-paragraph">I&#8217;m assuming this optimization will be worse when this workflow becomes subsumed in a larger one.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Building the app as commands makes it easy for Thaura to run it and debug it.</p>



<p class="wp-block-paragraph">However, to &#8220;tie it all together&#8221;, when I normally write programs like this by hand, I make libraries to standardize things like file naming, reading data files, and so forth. </p>



<p class="wp-block-paragraph">I didn&#8217;t do that with Thaura, so it produced a lot of repetitive code, and broken code. However, it eventually fixed the bugs. It just never did it with little abstractions.</p>



<p class="wp-block-paragraph">I should have used JSON or INI or whatever Thaura produced as the data format, and made a small library to create the filenames, load data records, write data records, enforce fields, etc. Then, I could see if Thaura could learn it and use it, and maybe save some tokens.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">I&#8217;m not sure how much time I saved by coding this with an LLM. A lot of the conversations were exploratory.</p><p>The post <a href="https://riceball.com/2026/06/26/thaura-work-vibe-coding-an-rss-feed-aggregator/">Thaura Work: Vibe Coding an RSS Feed Aggregator</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://riceball.com/2026/06/26/thaura-work-vibe-coding-an-rss-feed-aggregator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Thaura Prompt Collection</title>
		<link>https://riceball.com/2026/05/15/thaura-prompt-collection/</link>
					<comments>https://riceball.com/2026/05/15/thaura-prompt-collection/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 15 May 2026 09:36:46 +0000</pubDate>
				<category><![CDATA[Machine Learning]]></category>
		<category><![CDATA[Prompts]]></category>
		<category><![CDATA[Thaura]]></category>
		<guid isPermaLink="false">https://riceball.com/?p=29649</guid>

					<description><![CDATA[<p>Prepare for me, a one-liner headline and link that is only 8 words long, on the topic of &#8220;Los Angeles Mayor&#8217;s Race&#8221;, sourced from the most popular news websites. The headline should summarize and provoke a click. Make the headline short. The link should go to the story. For every headline, use the md format: [&#8230;]</p>
<p>The post <a href="https://riceball.com/2026/05/15/thaura-prompt-collection/">Thaura Prompt Collection</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></description>
										<content:encoded><![CDATA[<p class="wp-block-paragraph">Prepare for me, a one-liner headline and link that is only 8 words long, on the topic of &#8220;Los Angeles Mayor&#8217;s Race&#8221;, sourced from the most popular news websites. The headline should summarize and provoke a click. Make the headline short. The link should go to the story. For every headline, use the md format: <strong>Topic</strong>: Headline: link</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Repeat for the following topics: &#8220;Gaza War&#8221;, &#8220;Iran War&#8221;, &#8220;Gas Prices&#8221;, &#8220;Recession&#8221;, &#8220;Inflation&#8221;</p>



<p class="wp-block-paragraph">Repeat for the following topics: Gaza War, Iran War, US China Relations, California Governors, Voting Rights Act, Inflation Crisis, Economic Pain, Rise in Racism, Rise in Misogyny, Arcadia Mayor Resigns, Anti-Chinese Sentiment, Asian Americans and Gentrification, Los Angeles Mayor&#8217;s Race, Housing Affordability, Climate Change, Pollution, AI and Jobs, AI and War, Trade Conflicts, Healthcare, Health Insurance, Supply Chain, Interest Rates, Energy and Power</p>



<p class="wp-block-paragraph">(Note that the longer the list is, the more the LLM will hallucinate headlines and links. It also helps if the news source has an RSS feed. It seems to know how to use those, sometimes. Otherwise, I don&#8217;t know how it&#8217;s getting the news. I realize this makes the LLM sound like a shit version of an RSS feed aggregator.)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">for the wordpress contact form 7 plugin, create a form with the fields first name, email, phone, zip, and a list of checkboxes for the following categories of stuff to give away: vintage, electronics, computer parts, scrap metal, office supplies, art supplies, boxes, clothes, housewares, books.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">consider index_builder.py to be obsolete legacy within this new system. implementing new commands is acceptable.</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-11 wp-block-paragraph">(This helps to release Work from holding on to it&#8217;s goal of retaining backward compatibility.)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">update then clean up the docs</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-12 wp-block-paragraph">(Telling Work to work on the docs seems to help. It also externalizes some of the internal state into a file, so it can be re-read in the future, or in the event of a crash.)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">continue</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-13 wp-block-paragraph">(Work keeps a to do list and plans. It doesn&#8217;t always complete them. So &#8220;continue&#8221; will tell it to keep working on it.)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-14 wp-block-paragraph">Warm up the context:</p>



<p class="wp-block-paragraph">How does Thaura find dependencies between tasks in a task list?</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-15 wp-block-paragraph">Then, copy-paste a bunch of your recent chat messages about a project:</p>



<p class="wp-block-paragraph">Extract all the listed tasks or to-dos in this chat:</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-16 wp-block-paragraph">Then, tell it to try its best ordering the tasks, and also ask for additional info as needed:</p>



<p class="wp-block-paragraph">Please put these tasks in order. Ask if clarification is necessary. Keep refining the order until the list is in the correct order.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-17 wp-block-paragraph">Tell it to examine a prior project, and specifically note the features I feel are useful to copy. These features took a  long time to create.</p>



<p class="wp-block-paragraph">Look at ../Reading Material Archive.thaura/ and study the documents in there (but ignore the numbered folders because they are the actual document archives). study about how the PDF files were classified as books, journal articles, pdf prints of web pages, etc.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-18 wp-block-paragraph">After some time generating code, I ask it to consolidate repetitive code into libraries. (It saved several hundred lines of code.) I&#8217;m trying to use the DRY style of programming &#8211; bottom-up, where  you first produce code, then consolidate.</p>



<p class="wp-block-paragraph">are there opportunities to DRY out the code and put some functions into a library?</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-19 wp-block-paragraph">A vibe coded app was going really well, but it was really bugging me that the plugin extension system was obviously inefficient. So I started a new chat and did this:</p>



<p class="wp-block-paragraph">go into version 2, and do a cursory review of the code, and explore the extension system, and suggest ways to make it more efficient.</p>



<p class="has-windpress-red-600-color has-text-color has-link-color wp-elements-20 wp-block-paragraph">It went wild and found 9 optimizations, some obvious, some not, and most were pretty sophisticated. It took a lot of time, and it  had a huge back-and-forth with itself and lots of code edits to implement them all, undo some of them because they were mistakes, and so forth.</p>



<p class="wp-block-paragraph"><br></p><p>The post <a href="https://riceball.com/2026/05/15/thaura-prompt-collection/">Thaura Prompt Collection</a> first appeared on <a href="https://riceball.com">🍙</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://riceball.com/2026/05/15/thaura-prompt-collection/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
