<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>notes from /dev/null - automation</title><link href="http://yummymelon.com/devnull/" rel="alternate"/><link href="http://yummymelon.com/devnull/feeds/tags/automation.atom.xml" rel="self"/><id>http://yummymelon.com/devnull/</id><updated>2023-06-15T18:28:00-07:00</updated><entry><title>Sometimes you just want the dang thing to pass butter. Introducing launchutil.</title><link href="http://yummymelon.com/devnull/sometimes-you-just-want-the-dang-thing-to-pass-butter-introducing-launchutil.html" rel="alternate"/><published>2023-06-15T18:28:00-07:00</published><updated>2023-06-15T18:28:00-07:00</updated><author><name>Charles Choi</name></author><id>tag:yummymelon.com,2023-06-15:/devnull/sometimes-you-just-want-the-dang-thing-to-pass-butter-introducing-launchutil.html</id><summary type="html">&lt;p&gt;Introducing &lt;a href="https://github.com/kickingvegas/launchutil"&gt;launchutil&lt;/a&gt;, a helper utility to support creating and running a simple macOS &lt;code&gt;launchd&lt;/code&gt; service.&lt;/p&gt;</summary><content type="html">&lt;p&gt;There&amp;rsquo;s an old yet useful feature in macOS where you can get the time &lt;a href="https://support.apple.com/guide/mac-help/change-control-center-settings-mchlad96d366/13.0/mac/13.0"&gt;announced periodically&lt;/a&gt;, typically at the top of the hour.&lt;/p&gt;
&lt;p&gt;One thing though: Turning this feature on means &lt;em&gt;manually&lt;/em&gt; turning it on and having it repeatedly announce until you &lt;em&gt;manually&lt;/em&gt; turn it off. This begs for automation, especially if you want the time announced only during working hours (say 9am to 5pm). Since macOS is my daily driver, this is where we talk about &lt;a href="https://support.apple.com/guide/terminal/script-management-with-launchd-apdc6c1077b-5d5d-4d35-9c19-60f2397b2369/mac"&gt;launchd&lt;/a&gt;, which since Mac OS X 10.4 has been the system process that manages daemons and agents. There&amp;rsquo;s plenty of posts describing it and how &lt;code&gt;launchd&lt;/code&gt; works (see references below) so I won&amp;rsquo;t go over that here. What I will say though is that working with &lt;code&gt;launchd&lt;/code&gt; via the &lt;code&gt;launchctl&lt;/code&gt; command line utility is a PITA.&lt;/p&gt;
&lt;p&gt;By and large my pain points in using &lt;code&gt;launchctl&lt;/code&gt; are this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Creating a launch script means you need to write it in XML, following a schema that I find impossible to remember.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installing the launch script means you need to work with two different directories: 1) where you create the launch script and 2) where you need to install it (typically &lt;code&gt;$HOME/Library/LaunchAgents&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Managing the launch script (that is starting, stopping, getting its status) using &lt;code&gt;launchctl&lt;/code&gt; requires different references to the launch script/service which means different command line arguments to access the same thing. This makes the ergonomics of using &lt;code&gt;launchctl&lt;/code&gt; &lt;em&gt;punishing&lt;/em&gt;, especially when debugging the launch script.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having worked with macOS for well over a decade, I&amp;rsquo;ve felt the woe of writing a number of launch scripts and encountering all of the pain points above. So I&amp;rsquo;ve decided to do something about it.&lt;/p&gt;
&lt;p&gt;Introducing &lt;a href="https://github.com/kickingvegas/launchutil"&gt;launchutil&lt;/a&gt;, a helper utility to support creating and running a simple macOS &lt;code&gt;launchd&lt;/code&gt; service. It is written in Python and is expressly designed to have the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy creation of a working XML file for daily scheduling that you can edit to taste.&lt;/li&gt;
&lt;li&gt;Uses the launch script name as the reference to the job/service you want to run. (You can still use the service name though.)&lt;/li&gt;
&lt;li&gt;Easy installation and removal of the launch script.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s see &lt;code&gt;launchutil&lt;/code&gt; at work: Imagine that you want to create a job that invokes the &lt;code&gt;say&lt;/code&gt; command to say &amp;ldquo;hello there&amp;rdquo; at 14:00 (2pm) and 15:15 (3:15 pm) everyday. The command invocation would look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;--program&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/say&lt;span class="w"&gt; &lt;/span&gt;--program-arguments&lt;span class="w"&gt; &lt;/span&gt;hello&lt;span class="w"&gt; &lt;/span&gt;there&lt;span class="w"&gt; &lt;/span&gt;--daily&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:15&lt;span class="w"&gt; &lt;/span&gt;--execute&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Alternately, you can use short arguments to achieve the same result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/say&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;hello&lt;span class="w"&gt; &lt;/span&gt;there&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:15&lt;span class="w"&gt; &lt;/span&gt;-x&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running the above generates the launch script named &lt;code&gt;com.yummymelon.sayhello.plist&lt;/code&gt; whose contents are shown below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC &amp;quot;-//Apple//DTD PLIST 1.0//EN&amp;quot; &amp;quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0&amp;quot;&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="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.yummymelon.sayhello&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Program&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/bin/say&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/bin/say&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;hello&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;there&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;14&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&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="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;15&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;span class="w"&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="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;15&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="w"&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that the launch script file name is built off the service name. &lt;code&gt;launchutil&lt;/code&gt; relies on this convention to support the command-line ergonomics to &lt;em&gt;infer&lt;/em&gt; the service name from the launch script file name and vice-versa.&lt;/p&gt;
&lt;p&gt;Installing the launch script into the default directory &lt;code&gt;$HOME/Library/LaunchAgents&lt;/code&gt; is achieved with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello.plist&lt;span class="w"&gt; &lt;/span&gt;-x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To start the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello.plist&lt;span class="w"&gt; &lt;/span&gt;-x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To get the status of the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello.plist&lt;span class="w"&gt; &lt;/span&gt;-x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To stop the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello.plist&lt;span class="w"&gt; &lt;/span&gt;-x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To uninstall the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;launchutil&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;com.yummymelon.sayhello.plist&lt;span class="w"&gt; &lt;/span&gt;-x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that in all the above commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No navigation or references to the installed directory.&lt;/li&gt;
&lt;li&gt;Only reference the launch script file name (or service name).&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Say Time at the Top of the Hour&lt;/h1&gt;
&lt;p&gt;So as promised at the start of this post, a &lt;code&gt;launchutil&lt;/code&gt; example that will announce the time daily from 9am to 5pm can be found at &lt;a href="https://github.com/kickingvegas/launchutil/tree/main/examples"&gt;https://github.com/kickingvegas/launchutil/tree/main/examples&lt;/a&gt;  &lt;/p&gt;
&lt;h1&gt;Getting launchutil&lt;/h1&gt;
&lt;p&gt;If you&amp;rsquo;ve made all the way here and are still interested, you can get &lt;code&gt;launchutil&lt;/code&gt; at &lt;a href="https://github.com/kickingvegas/launchutil"&gt;https://github.com/kickingvegas/launchutil&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the sake of simplicity, there is no &lt;a href="https://pypi.org/project/pip/"&gt;pip · PyPI&lt;/a&gt; packaging; just install the single file Python 3 script into your &lt;code&gt;bin&lt;/code&gt; directory. It has no dependencies to any packages outside of the basic macOS install.&lt;/p&gt;
&lt;p&gt;If you have any feedback, please let me know at the GitHub &lt;a href="https://github.com/kickingvegas/launchutil/discussions"&gt;discussion&lt;/a&gt; board for &lt;code&gt;launchutil&lt;/code&gt;. &lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eclecticlight.co/2021/09/13/running-software-automatically-using-launchd/"&gt;Running software automatically using launchd – The Eclectic Light Company&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/"&gt;Launchctl 2.0 Syntax | Babo D&amp;rsquo;s Corner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eclecticlight.co/2023/03/11/whos-managing-my-apps/"&gt;Who’s managing my apps? – The Eclectic Light Company&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leancrew.com/all-this/2009/08/launchd-as-a-replacement-for-at/"&gt;Launchd as a replacement for at - All this&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"/><category term="python"/><category term="macos"/><category term="automation"/></entry></feed>