<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Vandy Sodanheang</title>
    <description>The latest articles on Forem by Vandy Sodanheang (@luciferrevenant).</description>
    <link>https://forem.com/luciferrevenant</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F888506%2Fce992670-89b6-49f5-ad91-d15fddc44029.jpeg</url>
      <title>Forem: Vandy Sodanheang</title>
      <link>https://forem.com/luciferrevenant</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/luciferrevenant"/>
    <language>en</language>
    <item>
      <title>I got tired of writing launchd XML by hand, so I built launchd-gen</title>
      <dc:creator>Vandy Sodanheang</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:05:31 +0000</pubDate>
      <link>https://forem.com/luciferrevenant/i-got-tired-of-writing-launchd-xml-by-hand-so-i-built-launchd-gen-1c6n</link>
      <guid>https://forem.com/luciferrevenant/i-got-tired-of-writing-launchd-xml-by-hand-so-i-built-launchd-gen-1c6n</guid>
      <description>&lt;p&gt;If you've ever tried to schedule a recurring job on macOS, you've met &lt;strong&gt;launchd&lt;/strong&gt;. And if you've spent more than five minutes with it, you've probably thought: &lt;em&gt;"Why is this so much harder than crontab?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the story of why I built &lt;a href="https://github.com/VandyTheCoder/launchd-gen" rel="noopener noreferrer"&gt;&lt;strong&gt;launchd-gen&lt;/strong&gt;&lt;/a&gt; — a small Go CLI that takes a familiar cron expression and emits a valid macOS launchd property list, ready to drop into &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with launchd
&lt;/h2&gt;

&lt;p&gt;launchd is the modern replacement for cron on macOS. It's more powerful (it can react to filesystem events, network changes, system load, etc.), but for the 80% case — &lt;em&gt;"run this script every weekday at 9 AM"&lt;/em&gt; — it's significantly more painful than crontab.&lt;/p&gt;

&lt;p&gt;Here's what a "run every weekday at 9 AM" looks like in cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 9 * * 1-5 /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. Five fields. Done.&lt;/p&gt;

&lt;p&gt;Here's what the equivalent looks like as a launchd plist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.me.my-script&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/local/bin/my-script&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five times the fields. Forty times the lines. And here's the part that really gets you:&lt;/p&gt;

&lt;h2&gt;
  
  
  launchd has no concept of ranges
&lt;/h2&gt;

&lt;p&gt;The reason that plist needs &lt;strong&gt;five&lt;/strong&gt; &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries is that launchd's &lt;code&gt;StartCalendarInterval&lt;/code&gt; only accepts &lt;strong&gt;single integer values per key&lt;/strong&gt;. There is no way to say "weekdays 1 through 5" in a single dict — you have to expand the range yourself by writing one dict per day.&lt;/p&gt;

&lt;p&gt;This is annoying for &lt;code&gt;1-5&lt;/code&gt;. It becomes ridiculous fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/15 9-17 * * 1-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's "every 15 minutes during business hours on weekdays" — a perfectly reasonable cron expression. To express it as a launchd plist, you have to write &lt;strong&gt;180 separate &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries&lt;/strong&gt;, because it's the cartesian product of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 minute values: &lt;code&gt;0, 15, 30, 45&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;9 hour values: &lt;code&gt;9, 10, 11, 12, 13, 14, 15, 16, 17&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;5 weekday values: &lt;code&gt;1, 2, 3, 4, 5&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;4 × 9 × 5 = 180&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nobody types that out by hand. They either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drop the schedule down to a coarser interval that's easier to write&lt;/li&gt;
&lt;li&gt;Write a script that loops &lt;code&gt;launchctl&lt;/code&gt; calls, defeating the purpose of having a declarative scheduler&lt;/li&gt;
&lt;li&gt;Use a third-party GUI tool like Lingon or LaunchControl&lt;/li&gt;
&lt;li&gt;Give up and stay on crontab, which Apple has been quietly trying to deprecate since 2005&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted option 5: &lt;strong&gt;a small CLI that takes a cron expression and outputs the right plist.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter launchd-gen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap VandyTheCoder/tools
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; launchd-gen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchd-gen &lt;span class="nt"&gt;--label&lt;/span&gt; com.me.my-script &lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt; /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The output is a fully-formed launchd plist on stdout, with all 5 weekday &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries expanded automatically.&lt;/p&gt;

&lt;p&gt;To install and load it in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchd-gen &lt;span class="nt"&gt;--install&lt;/span&gt; &lt;span class="nt"&gt;--load&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; com.me.my-script &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stdout&lt;/span&gt; /tmp/my-script.log &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stderr&lt;/span&gt; /tmp/my-script.err &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That writes the plist to &lt;code&gt;~/Library/LaunchAgents/com.me.my-script.plist&lt;/code&gt; and runs &lt;code&gt;launchctl load&lt;/code&gt; on it. From cron expression to running scheduled job in one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it supports
&lt;/h2&gt;

&lt;p&gt;The cron parser handles everything you'd expect from standard 5-field crontab:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9,17 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 and 17:00 daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 on weekdays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*/15 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range + step&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9-17/2 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00, 11:00, 13:00, 15:00, 17:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combinations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*/15 9-17 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 15 min, business hours, weekdays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shortcuts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@hourly&lt;/code&gt;, &lt;code&gt;@reboot&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, &lt;code&gt;@monthly&lt;/code&gt;, &lt;code&gt;@yearly&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shortcuts behave exactly as crontab — &lt;code&gt;@reboot&lt;/code&gt; becomes a &lt;code&gt;RunAtLoad: true&lt;/code&gt; flag, the rest expand to their equivalent 5-field expressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;launchd-gen is &lt;strong&gt;under 600 lines of production Go&lt;/strong&gt; (plus ~200 lines of tests), with &lt;strong&gt;zero external dependencies&lt;/strong&gt;. The whole thing is one binary, MIT licensed, and the source is on GitHub.&lt;/p&gt;

&lt;p&gt;The interesting bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;internal/cron/parser.go&lt;/code&gt;&lt;/strong&gt; — turns a cron string into a list of &lt;code&gt;Interval&lt;/code&gt; structs by computing the cartesian product of the parsed field values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;internal/plist/writer.go&lt;/code&gt;&lt;/strong&gt; — hand-writes the launchd XML rather than depending on a third-party plist library, which keeps the binary tiny and &lt;code&gt;go install&lt;/code&gt; instant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/strong&gt; — the CLI entry point, with a &lt;code&gt;--install&lt;/code&gt; flag that writes directly to &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt; and a &lt;code&gt;--load&lt;/code&gt; flag that chains a &lt;code&gt;launchctl load&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deliberately avoided pulling in cobra, viper, or any of the other usual Go CLI suspects. For something this small they're overkill, and the standard library's &lt;code&gt;flag&lt;/code&gt; package handles it just fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I run a small daily-dashboard project on my own infrastructure. It has four scheduled jobs — a news fetcher, a daily AI briefing, an activity log summarizer, and a weekly trade-config optimizer. Each one is a launchd agent, because that's how you do scheduled work on macOS in 2026.&lt;/p&gt;

&lt;p&gt;Writing those four plists by hand was the most painful part of the whole project. The actual &lt;em&gt;work&lt;/em&gt; the scripts do — fetching, summarizing, posting to a webhook — was easier than getting them on a schedule. That's a smell, and I wrote launchd-gen to fix it.&lt;/p&gt;

&lt;p&gt;The four real plists from that project are now used as acceptance fixtures in the launchd-gen test suite, which is the kind of dogfooding I always look for in a tool I'm about to depend on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;v0.1.0 is shipped and it does what I built it for. The roadmap for v0.2.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Day-of-week names (&lt;code&gt;MON&lt;/code&gt;, &lt;code&gt;TUE&lt;/code&gt;, &lt;code&gt;WED&lt;/code&gt;, ...) instead of numeric weekdays&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;--dry-run&lt;/code&gt; flag that prints the plist without writing it&lt;/li&gt;
&lt;li&gt;Reverse mode: convert an existing plist back into a cron expression&lt;/li&gt;
&lt;li&gt;Environment variable injection from a file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those sound useful — or if you want something else entirely — open an issue. Pull requests welcome, MIT licensed, no CLA, no committee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap VandyTheCoder/tools
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; launchd-gen
launchd-gen &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, issues, and releases: &lt;strong&gt;&lt;a href="https://github.com/VandyTheCoder/launchd-gen" rel="noopener noreferrer"&gt;github.com/VandyTheCoder/launchd-gen&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've ever stared at a launchd plist and wondered why something this conceptually simple has to be this verbose — this is for you.&lt;/p&gt;

</description>
      <category>go</category>
      <category>macos</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Redis Trail - Redis Hackathon</title>
      <dc:creator>Vandy Sodanheang</dc:creator>
      <pubDate>Mon, 29 Aug 2022 13:15:57 +0000</pubDate>
      <link>https://forem.com/luciferrevenant/redis-trail-redis-hackathon-1f5e</link>
      <guid>https://forem.com/luciferrevenant/redis-trail-redis-hackathon-1f5e</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;Redis Trail is an effective audit trail solution can be crucial to an organization's security and data integrity as it can help find the who, what, and when. This helps organizations keep track of changes and investigate potential mistakes or violations of policy. Redis Trail is built as an audit trail application using Spring Boot framework for the backend and Redis as stream storage. And, the client application can integrate with Redis Trail using RESTful API to keep and monitor track of change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Wacky Wildcards - Redis Trail will records all the data change record and enable the client application to fetch and filter data changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Video Explainer of My Project
&lt;/h3&gt;

&lt;p&gt;You can view our video here.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/q-ouExtPMpQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Used
&lt;/h3&gt;

&lt;p&gt;We use Spring Boot Framework with Kotlin Programming Language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;

&lt;p&gt;You can visit our source code via &lt;a href="https://github.com/soramitsukhmer-lab/redis-trail"&gt;Redis Trail&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spring.io/projects/spring-boot/"&gt;Spring Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-data/redis/docs/current/api/"&gt;Spring Data Redis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Collaborators
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a class="mentioned-user" href="https://dev.to/thayouly"&gt;@thayouly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="mentioned-user" href="https://dev.to/jessy_oeung"&gt;@jessy_oeung&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="mentioned-user" href="https://dev.to/huongsakada"&gt;@huongsakada&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/luciferrevenant"&gt;@luciferrevenant&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/lonelypotato"&gt;@lonelypotato&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/svannak"&gt;@svannak&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;In conclusion, we'd like to thank Redis and the community that create this amazing hackathon. We are really thrilled about our Redis Trail and hope our application can help someone or some organization in their business needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Check out &lt;a href="https://redis.io/docs/stack/get-started/clients/#high-level-client-libraries"&gt;Redis OM&lt;/a&gt;, client libraries for working with Redis as a multi-model database.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Use &lt;a href="https://redis.info/redisinsight"&gt;RedisInsight&lt;/a&gt; to visualize your data in Redis.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sign up for a &lt;a href="https://redis.info/try-free-dev-to"&gt;free Redis database&lt;/a&gt;.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>redishackathon</category>
      <category>springboot</category>
      <category>kotlin</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
