<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://spmse.github.io/quickli/blog</id>
    <title>quiCkLI Blog</title>
    <updated>2026-07-31T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://spmse.github.io/quickli/blog"/>
    <subtitle>News, tutorials, and deep dives from the quiCkLI project.</subtitle>
    <icon>https://spmse.github.io/quickli/img/quickli-icon-light.svg</icon>
    <rights>Copyright © 2026 quiCkLI contributors.</rights>
    <entry>
        <title type="html"><![CDATA[Introducing quiCkLI]]></title>
        <id>https://spmse.github.io/quickli/blog/2026/07/31/introducing-quickli</id>
        <link href="https://spmse.github.io/quickli/blog/2026/07/31/introducing-quickli"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What quiCkLI is, who it is for, and what to expect from this blog.]]></summary>
        <content type="html"><![CDATA[<p>quiCkLI is a small, educational Python framework for building command-line applications.
It keeps registration, parsing, validation, dispatch, and help visible so developers can
learn how CLI tools fit together.</p>
<!-- -->
<p>This blog will combine practical tutorials, design notes, comparisons, and reflections on
where command-line applications remain useful. The goal is to make CLI development easier
to understand and more enjoyable to explore.</p>
<div class="theme-admonition theme-admonition-tip admonition_Qx6W alert alert--success"><div class="admonitionHeading_S1FI"><span class="admonitionIcon_VL2H"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_PclH"><p>Start with the <a class="" href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">Getting Started series</a> if you want
to build something immediately.</p></div></div>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="General" term="General"/>
        <category label="quickli" term="quickli"/>
        <category label="introduction" term="introduction"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting Started with quiCkLI: Your First Command-Line Application]]></title>
        <id>https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world</id>
        <link href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Build your first Python command-line application with quiCkLI in under five minutes. This tutorial covers Application, Argument, and Option  -  the three primitives you need to know to get started.
]]></summary>
        <content type="html"><![CDATA[<p>If you have ever wanted to build a small Python command-line tool but found larger
frameworks overwhelming, <code>quiCkLI</code> was designed for you. It is a minimal framework
that keeps the essentials visible so you can learn  -  and build  -  without unnecessary
complexity.</p>
<p>In this first article of the <em>Getting Started with quiCkLI</em> series you will build a
greeting tool with exactly three concepts: <code>Application</code>, <code>Argument</code>, and <code>Option</code>.</p>
<!-- -->
<aside class="blog-series-navigation" aria-label="Series"><p><strong>Getting Started with quiCkLI</strong></p><p>Part 1 of 3</p><ol><li aria-current="page"><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">1<!-- -->. <!-- -->Your First Command-Line Application</a></li><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">2<!-- -->. <!-- -->Reading Files and Validating Input</a></li><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli">3<!-- -->. <!-- -->Multi-Command Applications</a></li></ol><p><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">Next<!-- --> →</a></p><p><a href="https://spmse.github.io/quickli/blog/series">View all series</a></p></aside>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-will-build">What you will build<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#what-you-will-build" class="hash-link" aria-label="Direct link to What you will build" title="Direct link to What you will build" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python hello.py Ada</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ Hello, Ada</span><span class="token operator" style="color:#393A34">!</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python hello.py Ada </span><span class="token parameter variable" style="color:#36acaa">--uppercase</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ HELLO, ADA</span><span class="token operator" style="color:#393A34">!</span><br></div></code></pre></div></div>
<p>A single file. No configuration. Under thirty lines of Python.</p>
<div class="theme-admonition theme-admonition-tip admonition_Qx6W alert alert--success"><div class="admonitionHeading_S1FI"><span class="admonitionIcon_VL2H"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_PclH"><p><code>Application.run()</code> returns the handler result. A real executable wrapper still needs to
print that result and decide how to map errors to exit codes.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="prerequisites">Prerequisites<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<p>Install quickli in a virtual environment:</p>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python </span><span class="token parameter variable" style="color:#36acaa">-m</span><span class="token plain"> venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ </span><span class="token builtin class-name">source</span><span class="token plain"> .venv/bin/activate   </span><span class="token comment" style="color:#999988;font-style:italic"># Windows: .venv\Scripts\activate</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ pip </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> quickli</span><br></div></code></pre></div></div>
<p>You need Python 3.12 or later.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="step-1-create-the-application">Step 1: Create the application<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#step-1-create-the-application" class="hash-link" aria-label="Direct link to Step 1: Create the application" title="Direct link to Step 1: Create the application" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> quickli </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Application</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    description</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Greet a person from the command line."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p><code>Application</code> is the root container. It owns command registration and dispatch. You give
it a name (used in help output) and a short description.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="step-2-register-a-handler">Step 2: Register a handler<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#step-2-register-a-handler" class="hash-link" aria-label="Direct link to Step 2: Register a handler" title="Direct link to Step 2: Register a handler" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> quickli </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Argument</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Option</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">entrypoint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print a greeting."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    arguments</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">Argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Name to greet."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"uppercase"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"u"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print the greeting in uppercase."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">greet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> uppercase</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    message </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"Hello, </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">!"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> message</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">upper</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> uppercase </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> message</span><br></div></code></pre></div></div>
<p><code>@app.entrypoint</code> registers a function as the single handler for this application.</p>
<ul>
<li class=""><code>Argument("name")</code> declares a required positional value. quickli passes it directly to
the <code>name</code> parameter of your function.</li>
<li class=""><code>Option("uppercase", is_flag=True)</code> declares a boolean flag. Present → <code>True</code>,
absent → <code>False</code>.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="step-3-run-the-application">Step 3: Run the application<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#step-3-run-the-application" class="hash-link" aria-label="Direct link to Step 3: Run the application" title="Direct link to Step 3: Run the application" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p><code>Application.run()</code> reads <code>sys.argv[1:]</code> by default and returns the handler result. You
decide what to do with it  -  in this case, you print it.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-full-file">The full file<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#the-full-file" class="hash-link" aria-label="Direct link to The full file" title="Direct link to The full file" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> __future__ </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> annotations</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> quickli </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Argument</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Option</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    description</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Greet a person from the command line."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">entrypoint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print a greeting."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    arguments</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">Argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Name to greet."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"uppercase"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"u"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print the greeting in uppercase."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">greet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> uppercase</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    message </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"Hello, </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">!"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> message</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">upper</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> uppercase </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> message</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="try-it">Try it<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python hello.py Ada           </span><span class="token comment" style="color:#999988;font-style:italic"># Hello, Ada!</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python hello.py Ada </span><span class="token parameter variable" style="color:#36acaa">-u</span><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># HELLO, ADA!</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python hello.py               </span><span class="token comment" style="color:#999988;font-style:italic"># (help output)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-learned">What you learned<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#what-you-learned" class="hash-link" aria-label="Direct link to What you learned" title="Direct link to What you learned" translate="no">​</a></h2>
<ul>
<li class=""><code>Application</code> owns registration and dispatch.</li>
<li class=""><code>@app.entrypoint</code> registers a single handler for the whole application.</li>
<li class=""><code>Argument</code> describes positional, required input.</li>
<li class=""><code>Option</code> with <code>is_flag=True</code> adds a boolean switch.</li>
<li class=""><code>Application.run(argv)</code> returns the result  -  you control output.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="next-in-this-series">Next in this series<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world#next-in-this-series" class="hash-link" aria-label="Direct link to Next in this series" title="Direct link to Next in this series" translate="no">​</a></h2>
<p>The next article adds <strong>file input</strong>, <strong>validation</strong>, and <strong>global options</strong> by building a
small file viewer inspired by the Unix <code>cat</code> command.</p>
<p>📖 <a class="" href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">Part 2: Reading Files  -  the quickcat tool →</a></p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="tutorial" term="tutorial"/>
        <category label="Getting Started" term="Getting Started"/>
        <category label="quickli" term="quickli"/>
        <category label="python" term="python"/>
        <category label="cli" term="cli"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building quiCkLI: Why I Started a Minimal CLI Framework]]></title>
        <id>https://spmse.github.io/quickli/blog/building-quickli-01-motivation</id>
        <link href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why build another CLI framework? This first post in the Building quiCkLI series explores the motivation behind the project, what problem it solves for learners, and the design principles that guide every decision.
]]></summary>
        <content type="html"><![CDATA[<p>There are already excellent Python CLI frameworks. <code>argparse</code> is in the standard library.
<code>click</code> is polished and widely used. <code>typer</code> generates interfaces from type annotations.
If these exist, why build another one?</p>
<p>This post answers that question honestly. It is the first in a series about the
development of <code>quiCkLI</code>  -  what it is, why it exists, and where it is going.</p>
<!-- -->
<aside class="blog-series-navigation" aria-label="Series"><p><strong>Building quiCkLI</strong></p><p>Part 1 of 2</p><ol><li aria-current="page"><a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation">1<!-- -->. <!-- -->Why I Started a Minimal CLI Framework</a></li><li><a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience">2<!-- -->. <!-- -->Who Is This Framework For?</a></li></ol><p><a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience">Next<!-- --> →</a></p><p><a href="https://spmse.github.io/quickli/blog/series">View all series</a></p></aside>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-gap-i-wanted-to-fill">The gap I wanted to fill<a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation#the-gap-i-wanted-to-fill" class="hash-link" aria-label="Direct link to The gap I wanted to fill" title="Direct link to The gap I wanted to fill" translate="no">​</a></h2>
<p>I wanted a framework I could point a learner at and say: <em>all the important parts are
visible here</em>. When I tried to explain how a CLI application works using an existing
framework, I kept running into the same problem. The framework was good at hiding
complexity  -  which is great for production code  -  but that same hiding made it hard to
understand what was actually happening.</p>
<p><code>click</code>, for example, uses a layered decorator model that works beautifully in practice
but abstracts away how arguments are collected, how defaults are resolved, and how
subcommands are dispatched. That abstraction is valuable. It is also precisely what makes
it hard to learn from.</p>
<p>I did not want to replace any of these tools. I wanted to build something that could serve
as a reference implementation  -  a framework small enough that a developer could read the
entire source in an afternoon and understand how a CLI is assembled from first principles.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-minimal-means-here">What "minimal" means here<a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation#what-minimal-means-here" class="hash-link" aria-label="Direct link to What &quot;minimal&quot; means here" title="Direct link to What &quot;minimal&quot; means here" translate="no">​</a></h2>
<p><code>quickli</code> is not minimal because it has fewer features. It is minimal in the sense that
every part of the design is deliberate and visible.</p>
<ul>
<li class="">The application container owns registration and dispatch. That boundary is explicit.</li>
<li class="">Arguments are positional and ordered. Options are named. Those are different things.</li>
<li class="">Conversion and validation are separate steps, and they happen before the handler runs.</li>
<li class="">The framework returns results to the caller. It reads <code>sys.argv[1:]</code> by default, but
does not print output or handle exit codes.</li>
</ul>
<p>Each of these decisions was made because it keeps the mechanics of a CLI visible to
someone learning how they work.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-guiding-question">The guiding question<a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation#the-guiding-question" class="hash-link" aria-label="Direct link to The guiding question" title="Direct link to The guiding question" translate="no">​</a></h2>
<p>For every feature I considered adding to <code>quickli</code>, I asked the same question: <em>does this
make the framework easier to learn from, or does it make it more powerful at the cost of
making it harder to understand?</em></p>
<p>That question has kept <code>quickli</code> small. It has also forced me to be explicit about what
the framework is not trying to do.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-comes-next">What comes next<a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation#what-comes-next" class="hash-link" aria-label="Direct link to What comes next" title="Direct link to What comes next" translate="no">​</a></h2>
<p>The next post in this series looks at who <code>quiCkLI</code> is for  -  the target audience, and
what I am hoping they get out of using it.</p>
<p>📖 <a class="" href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience">Part 2: Who Is quiCkLI For? →</a></p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="Building quiCkLI" term="Building quiCkLI"/>
        <category label="meta" term="meta"/>
        <category label="motivation" term="motivation"/>
        <category label="design" term="design"/>
        <category label="quickli" term="quickli"/>
        <category label="open-source" term="open-source"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting Started with quiCkLI: Designing CLI Applications]]></title>
        <id>https://spmse.github.io/quickli/blog/2026/08/11/designing-cli-apps</id>
        <link href="https://spmse.github.io/quickli/blog/2026/08/11/designing-cli-apps"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The challenges, opportunities, and caveats involved in designing a CLI application.]]></summary>
        <content type="html"><![CDATA[<p>Designing a CLI is more than mapping strings to functions. You need to decide how users
discover commands, provide values, recover from mistakes, and compose tools in scripts.</p>
<!-- -->
<p>quiCkLI makes these decisions explicit: applications own dispatch, commands describe
operations, and arguments and options describe inputs. That simplicity is useful, but it
also means the caller still owns process output and exit codes.</p>
<div class="theme-admonition theme-admonition-note admonition_Qx6W alert alert--secondary"><div class="admonitionHeading_S1FI"><span class="admonitionIcon_VL2H"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_PclH"><p>Treat the boundary between the framework and the executable wrapper as a design decision,
not an implementation detail.</p></div></div>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="Getting Started" term="Getting Started"/>
        <category label="quickli" term="quickli"/>
        <category label="cli" term="cli"/>
        <category label="design" term="design"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting Started with quiCkLI: Reading Files and Validating Input]]></title>
        <id>https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools</id>
        <link href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Learn how to accept file paths, validate them before your handler runs, and use global options that apply to every command in your application. We build quickcat, a minimal cat-like file viewer.
]]></summary>
        <content type="html"><![CDATA[<p>In <a class="" href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">Part 1</a> you built a greeting tool with a
single argument and a flag. In this article you will add two new ideas:
<strong>validators</strong> that reject invalid input before your handler runs, and <strong>global options</strong>
that apply to every command in an application.</p>
<p>The example is <code>quickcat</code>, a minimal file viewer modeled after the Unix <code>cat</code> command.</p>
<div class="theme-admonition theme-admonition-note admonition_Qx6W alert alert--secondary"><div class="admonitionHeading_S1FI"><span class="admonitionIcon_VL2H"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_PclH"><p><code>Application.run()</code> reads <code>sys.argv[1:]</code> by default. Pass an explicit list to override,
or set <code>auto_sys_argv=False</code> to disable automatic reading.</p></div></div>
<!-- -->
<aside class="blog-series-navigation" aria-label="Series"><p><strong>Getting Started with quiCkLI</strong></p><p>Part 2 of 3</p><ol><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">1<!-- -->. <!-- -->Your First Command-Line Application</a></li><li aria-current="page"><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">2<!-- -->. <!-- -->Reading Files and Validating Input</a></li><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli">3<!-- -->. <!-- -->Multi-Command Applications</a></li></ol><p><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">← <!-- -->Previous</a> · <a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli">Next<!-- --> →</a></p><p><a href="https://spmse.github.io/quickli/blog/series">View all series</a></p></aside>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-will-build">What you will build<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#what-you-will-build" class="hash-link" aria-label="Direct link to What you will build" title="Direct link to What you will build" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python quickcat.py README.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python quickcat.py README.md </span><span class="token parameter variable" style="color:#36acaa">--number</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python quickcat.py README.md </span><span class="token parameter variable" style="color:#36acaa">-i</span><span class="token plain"> CONTRIBUTING.md </span><span class="token parameter variable" style="color:#36acaa">--verbose</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="new-concepts">New concepts<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#new-concepts" class="hash-link" aria-label="Direct link to New concepts" title="Direct link to New concepts" translate="no">​</a></h2>
<table><thead><tr><th>Concept</th><th>Purpose</th></tr></thead><tbody><tr><td><code>file_path()</code></td><td>Built-in validator that checks whether a path points to a real file</td></tr><tr><td><code>global_options</code></td><td>Options that appear on <code>Application</code> and apply to every handler</td></tr><tr><td><code>multiple=True</code></td><td>Accept the same option more than once and collect values into a list</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="global-options">Global options<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#global-options" class="hash-link" aria-label="Direct link to Global options" title="Direct link to Global options" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"quickcat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    description</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"A tiny cat-like CLI built with quickli."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    global_options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verbose"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Enable verbose output."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Global options live on <code>Application</code>, not on an individual command. They can appear before
or after the command name on the command line, and every handler that wants them declares
the matching parameter.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-file_path-validator">The <code>file_path</code> validator<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#the-file_path-validator" class="hash-link" aria-label="Direct link to the-file_path-validator" title="Direct link to the-file_path-validator" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">Argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Primary file path."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> validators</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">file_path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p><code>file_path()</code> is a built-in validator factory. It returns a callable that quickli runs
after parsing but before calling your handler. If the path does not exist or is not a
file, execution stops with a clear error message  -  your handler never sees an invalid
value.</p>
<p>You can pass multiple validators in the list; they run left to right.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="repeatable-options">Repeatable options<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#repeatable-options" class="hash-link" aria-label="Direct link to Repeatable options" title="Direct link to Repeatable options" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"include"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"i"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    multiple</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    validators</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">file_path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Additional file paths to print after the primary file."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p><code>multiple=True</code> lets users pass the option more than once:</p>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python quickcat.py main.py </span><span class="token parameter variable" style="color:#36acaa">-i</span><span class="token plain"> utils.py </span><span class="token parameter variable" style="color:#36acaa">-i</span><span class="token plain"> helpers.py</span><br></div></code></pre></div></div>
<p>The handler receives <code>include</code> as <code>list[Path] | None</code>. When absent, it is <code>None</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-full-example">The full example<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#the-full-example" class="hash-link" aria-label="Direct link to The full example" title="Direct link to The full example" translate="no">​</a></h2>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> __future__ </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> annotations</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> quickli </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Argument</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Option</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file_path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"quickcat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    description</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"A tiny cat-like CLI built with quickli."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    global_options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verbose"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Enable verbose output."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">entrypoint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print one or more text files to stdout."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    arguments</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Primary file path."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> validators</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">file_path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"encoding"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"e"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> default</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Text encoding."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"number"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"n"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Print line numbers."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"include"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"i"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            multiple</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            validators</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">file_path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Additional file paths to print after the primary file."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">show</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    encoding</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    number</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    include</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    verbose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    input_paths </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">include </span><span class="token keyword" style="color:#00009f">or</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    rendered_chunks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> input_path </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> input_paths</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> input_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">encoding</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lines </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">splitlines</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> number</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            lines </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">index</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">&gt;4</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">  </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">line</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> index</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> line </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">enumerate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lines</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> start</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> verbose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            rendered_chunks</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"==&gt; </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">input_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> &lt;=="</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        rendered_chunks</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lines</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">chunk </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> chunk </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> rendered_chunks </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-learned">What you learned<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#what-you-learned" class="hash-link" aria-label="Direct link to What you learned" title="Direct link to What you learned" translate="no">​</a></h2>
<ul>
<li class=""><code>global_options</code> on <code>Application</code> declare options available to every handler.</li>
<li class=""><code>file_path()</code> validates that a value is an existing, readable file.</li>
<li class=""><code>multiple=True</code> accumulates repeated option values into a list.</li>
<li class="">Validators run before the handler, keeping the handler body clean.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="next-in-this-series">Next in this series<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools#next-in-this-series" class="hash-link" aria-label="Direct link to Next in this series" title="Direct link to Next in this series" translate="no">​</a></h2>
<p>The final article builds a kubectl-like multi-command application with <code>@app.command</code>,
custom validator factories, and <code>CommandExecutionError</code>.</p>
<p>📖 <a class="" href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli">Part 3: Multi-Command Applications →</a></p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="tutorial" term="tutorial"/>
        <category label="quickli" term="quickli"/>
        <category label="python" term="python"/>
        <category label="cli" term="cli"/>
        <category label="validators" term="validators"/>
        <category label="file-path" term="file-path"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building quiCkLI: Who Is This Framework For?]]></title>
        <id>https://spmse.github.io/quickli/blog/building-quickli-02-target-audience</id>
        <link href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[quiCkLI is not a replacement for production CLI frameworks. This post defines the target audience  -  developers who want to understand how CLI tools are built  -  and explains what that focus means for the design.
]]></summary>
        <content type="html"><![CDATA[<p>Every design decision in <code>quiCkLI</code> was made with a specific kind of developer in mind. In
this post I want to be explicit about who that is, because it explains decisions that
might otherwise seem arbitrary.</p>
<!-- -->
<aside class="blog-series-navigation" aria-label="Series"><p><strong>Building quiCkLI</strong></p><p>Part 2 of 2</p><ol><li><a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation">1<!-- -->. <!-- -->Why I Started a Minimal CLI Framework</a></li><li aria-current="page"><a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience">2<!-- -->. <!-- -->Who Is This Framework For?</a></li></ol><p><a href="https://spmse.github.io/quickli/blog/building-quickli-01-motivation">← <!-- -->Previous</a></p><p><a href="https://spmse.github.io/quickli/blog/series">View all series</a></p></aside>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-primary-audience">The primary audience<a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience#the-primary-audience" class="hash-link" aria-label="Direct link to The primary audience" title="Direct link to The primary audience" translate="no">​</a></h2>
<p><code>quiCkLI</code> is for developers who want to understand how a command-line application is
assembled  -  not just use one.</p>
<p>That group includes:</p>
<ul>
<li class=""><strong>Students and early-career developers</strong> building their first command-line tools. They
need a framework where the mechanics are visible, not hidden behind a polished
abstraction.</li>
<li class=""><strong>Developers coming from other languages</strong> who want to understand the Python approach
to CLI design without reading the source of a large framework.</li>
<li class=""><strong>Experienced developers</strong> who want a small, readable reference implementation to use
as a teaching aid or a starting point for their own experiments.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="who-quickli-is-not-for">Who quiCkLI is not for<a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience#who-quickli-is-not-for" class="hash-link" aria-label="Direct link to Who quiCkLI is not for" title="Direct link to Who quiCkLI is not for" translate="no">​</a></h2>
<p>If you are building a production CLI tool and you need shell completion, nested
subcommands, configuration files, and a rich help renderer, use <code>click</code> or <code>typer</code>. They
are excellent tools built for exactly that purpose.</p>
<p><code>quiCkLI</code> makes explicit tradeoffs that would be unacceptable in a production framework:</p>
<ul>
<li class="">It reads <code>sys.argv[1:]</code> by default, but lets you opt out with <code>auto_sys_argv=False</code>.</li>
<li class="">It does not print output or choose exit codes.</li>
<li class="">It does not provide shell completion.</li>
<li class="">Its plugin discovery is manual.</li>
</ul>
<p>These are not oversights. They are deliberate choices that keep the parts visible.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-the-target-audience-needs">What the target audience needs<a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience#what-the-target-audience-needs" class="hash-link" aria-label="Direct link to What the target audience needs" title="Direct link to What the target audience needs" translate="no">​</a></h2>
<p>A learner needs:</p>
<ol>
<li class=""><strong>A small surface area.</strong> If the framework has twenty classes, understanding it
requires understanding all twenty. <code>quickli</code> has five core concepts. You can hold the
whole model in your head.</li>
<li class=""><strong>Explicit boundaries.</strong> Where does the framework end and the application begin? In
<code>quickli</code>, that boundary is <code>Application.run()</code>. The application receives a result back.
Everything that happens to process exit codes and output formatting is the
application's responsibility.</li>
<li class=""><strong>Runnable examples.</strong> Theory is not enough. The reference examples in the repository
are real, runnable tools that demonstrate each concept in context.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-this-means-for-the-project">What this means for the project<a href="https://spmse.github.io/quickli/blog/building-quickli-02-target-audience#what-this-means-for-the-project" class="hash-link" aria-label="Direct link to What this means for the project" title="Direct link to What this means for the project" translate="no">​</a></h2>
<p>Keeping <code>quiCkLI</code> useful for learners means resisting the natural impulse to add
features. Every feature adds surface area. Every abstraction hides something.</p>
<p>The project's quality bar is not just <em>does it work</em>  -  it is <em>is it still easy to
understand</em>. That is a harder standard to maintain, and it is the one that matters most
for this audience.</p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="Building quiCkLI" term="Building quiCkLI"/>
        <category label="meta" term="meta"/>
        <category label="target-audience" term="target-audience"/>
        <category label="design" term="design"/>
        <category label="quickli" term="quickli"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting Started with quiCkLI: Multi-Command Applications]]></title>
        <id>https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli</id>
        <link href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Build a kubectl-like CLI with multiple named commands, shared global options, custom validators, and explicit error handling. This final part of the Getting Started series shows how all the quickli primitives fit together.
]]></summary>
        <content type="html"><![CDATA[<p>In the first two parts of this series you built a greeting tool and a file viewer. In this
final article you will combine everything you have learned to build <code>pyk5l</code>, a
kubectl-like multi-command application.</p>
<p>By the end you will understand every primitive in <code>quickli</code> and how they fit together in
a realistic application.</p>
<!-- -->
<aside class="blog-series-navigation" aria-label="Series"><p><strong>Getting Started with quiCkLI</strong></p><p>Part 3 of 3</p><ol><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-01-hello-world">1<!-- -->. <!-- -->Your First Command-Line Application</a></li><li><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">2<!-- -->. <!-- -->Reading Files and Validating Input</a></li><li aria-current="page"><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli">3<!-- -->. <!-- -->Multi-Command Applications</a></li></ol><p><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-02-file-tools">← <!-- -->Previous</a></p><p><a href="https://spmse.github.io/quickli/blog/series">View all series</a></p></aside>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-will-build">What you will build<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#what-you-will-build" class="hash-link" aria-label="Direct link to What you will build" title="Direct link to What you will build" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py get pods</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py get pods </span><span class="token parameter variable" style="color:#36acaa">--output</span><span class="token plain"> json </span><span class="token parameter variable" style="color:#36acaa">--namespace</span><span class="token plain"> ops</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py describe pod api-7d4f5f6b89-l2xq9</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py logs api-7d4f5f6b89-l2xq9 </span><span class="token parameter variable" style="color:#36acaa">--tail</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--timestamps</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py apply manifests/web-pod.yaml --dry-run</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="appcommand-vs-appentrypoint"><code>@app.command</code> vs <code>@app.entrypoint</code><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#appcommand-vs-appentrypoint" class="hash-link" aria-label="Direct link to appcommand-vs-appentrypoint" title="Direct link to appcommand-vs-appentrypoint" translate="no">​</a></h2>
<p>Until now you used <code>@app.entrypoint</code>, which registers a single handler for the whole
application. When you need multiple commands, use <code>@app.command</code> instead:</p>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">command</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"get"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    help_text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"List resources in the selected namespace."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    arguments</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><br></div></code></pre></div></div>
<p>The <code>name</code> parameter becomes the first token on the command line. Omitting <code>name</code> uses
the function name. You can register as many commands as you need.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="custom-validator-factories">Custom validator factories<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#custom-validator-factories" class="hash-link" aria-label="Direct link to Custom validator factories" title="Direct link to Custom validator factories" translate="no">​</a></h2>
<p>quickli ships with built-in validators (<code>file_path</code>, <code>positive_number</code>, and others), but
you can write your own using the same pattern:</p>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">one_of</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">choices</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    allowed </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">tuple</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">choices</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    description </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"one of: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">", "</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">allowed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">validate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">object</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">object</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> allowed</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Expected one of: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation string" style="color:#e3116c">', '</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">join</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">allowed</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> value</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    validate</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">description </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> description</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> validate</span><br></div></code></pre></div></div>
<p>A validator factory returns a callable that:</p>
<ol>
<li class="">Receives the converted value.</li>
<li class="">Returns the value unchanged if it is valid.</li>
<li class="">Raises <code>ValueError</code> with a clear message if it is not.</li>
</ol>
<p>Setting <code>validate.description</code> provides the text that appears in help and error output.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="commandexecutionerror"><code>CommandExecutionError</code><a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#commandexecutionerror" class="hash-link" aria-label="Direct link to commandexecutionerror" title="Direct link to commandexecutionerror" translate="no">​</a></h2>
<p><code>CommandExecutionError</code> is for expected domain-level failures  -  not programming errors:</p>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">_find_pod</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> Pod</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> pod </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> PODS</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> pod</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">namespace </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> namespace </span><span class="token keyword" style="color:#00009f">and</span><span class="token plain"> pod</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> CommandExecutionError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-interpolation string" style="color:#e3116c">f"Pod '</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">' was not found in namespace '</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">namespace</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">'."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>This exception propagates unchanged out of <code>Application.run()</code>. Your application wrapper
can catch it and turn it into an exit code:</p>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> sys</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">try</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">except</span><span class="token plain"> quickli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">CommandExecutionError </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> error</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Error: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">error</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">file</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">sys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">stderr</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    sys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">exit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="global-options-shared-across-commands">Global options shared across commands<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#global-options-shared-across-commands" class="hash-link" aria-label="Direct link to Global options shared across commands" title="Direct link to Global options shared across commands" translate="no">​</a></h2>
<p>Declare options once on <code>Application</code>. Every command handler that needs them declares the
same parameter name:</p>
<div class="language-python codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-python codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Application</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"pyk5l"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    global_options</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"namespace"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"n"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> default</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"output"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"o"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> default</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"table"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">               validators</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">one_of</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"table"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"wide"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Option</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verbose"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> short_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> is_flag</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">command</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"get"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> output</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"table"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        verbose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@app</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">command</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"describe"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">describe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">             verbose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> output</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"table"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><br></div></code></pre></div></div>
<p>quickli matches global option names to parameter names for each command.</p>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="full-source">Full source<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#full-source" class="hash-link" aria-label="Direct link to Full source" title="Direct link to Full source" translate="no">​</a></h2>
<p>The complete example is at
<a href="https://github.com/spmse/quickli/blob/main/packages/core/examples/complex/pyk5l/app.py" target="_blank" rel="noopener noreferrer" class=""><code>packages/core/examples/complex/pyk5l/app.py</code></a>.
Try it:</p>
<div class="language-bash codeBlockContainer_Dek1 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QEAE"><pre tabindex="0" class="prism-code language-bash codeBlock_OciL thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines__6Ls"><div class="token-line" style="color:#393A34"><span class="token plain">$ </span><span class="token function" style="color:#d73a49">git</span><span class="token plain"> clone https://github.com/spmse/quickli.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ </span><span class="token builtin class-name">cd</span><span class="token plain"> quickli</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ pip </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-e</span><span class="token plain"> packages/core</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ </span><span class="token builtin class-name">cd</span><span class="token plain"> packages/core/examples/complex/pyk5l</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ python app.py get pods</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="what-you-learned">What you learned<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#what-you-learned" class="hash-link" aria-label="Direct link to What you learned" title="Direct link to What you learned" translate="no">​</a></h2>
<ul>
<li class=""><code>@app.command</code> registers named commands in a multi-command application.</li>
<li class="">Custom validator factories follow the same pattern as built-in validators.</li>
<li class=""><code>CommandExecutionError</code> signals expected domain-level failures.</li>
<li class="">Global options declared on <code>Application</code> are available to every command handler.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_s_6P" id="the-complete-quickli-primitives">The complete quiCkLI primitives<a href="https://spmse.github.io/quickli/blog/quickli-tutorial-03-multi-command-cli#the-complete-quickli-primitives" class="hash-link" aria-label="Direct link to The complete quiCkLI primitives" title="Direct link to The complete quiCkLI primitives" translate="no">​</a></h2>
<table><thead><tr><th>Primitive</th><th>Purpose</th></tr></thead><tbody><tr><td><code>Application</code></td><td>Root container; owns dispatch and registration</td></tr><tr><td><code>Command</code></td><td>Named operation (registered via <code>@app.command</code>)</td></tr><tr><td><code>Argument</code></td><td>Positional, ordered input</td></tr><tr><td><code>Option</code></td><td>Named input; flags, repeatable values, converters, validators</td></tr><tr><td><code>Plugin</code></td><td>Reusable command bundle loaded into an application</td></tr></tbody></table>
<p>Read the <a class="" href="https://spmse.github.io/quickli/docs/concepts/quickli-concepts">concept reference</a> for the full API or explore
the <a class="" href="https://spmse.github.io/quickli/docs/guides">implementation guides</a> to follow along with each example in detail.</p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="tutorial" term="tutorial"/>
        <category label="quickli" term="quickli"/>
        <category label="python" term="python"/>
        <category label="cli" term="cli"/>
        <category label="multi-command" term="multi-command"/>
        <category label="commands" term="commands"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why CLIs Still Matter for Operations]]></title>
        <id>https://spmse.github.io/quickli/blog/2026/08/25/clis-for-operations</id>
        <link href="https://spmse.github.io/quickli/blog/2026/08/25/clis-for-operations"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why system administration, automation, DevOps, and SRE continue to rely on command-line tools.]]></summary>
        <content type="html"><![CDATA[<p>Operations work values repeatability, composability, low overhead, and precise control.
Those properties are natural strengths of command-line tools, especially when a task must
run from a shell, a scheduled job, a container, or a recovery environment.</p>
<!-- -->
<p>CLIs also create durable interfaces between people, scripts, services, and infrastructure.
Their limitations are real, but a focused command with predictable text and exit behavior
can remain useful for years.</p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="General" term="General"/>
        <category label="quickli" term="quickli"/>
        <category label="cli" term="cli"/>
        <category label="devops" term="devops"/>
        <category label="sre" term="sre"/>
        <category label="automation" term="automation"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why CLIs Still Matter for AI Agents]]></title>
        <id>https://spmse.github.io/quickli/blog/2026/08/27/clis-for-ai-agents</id>
        <link href="https://spmse.github.io/quickli/blog/2026/08/27/clis-for-ai-agents"/>
        <updated>2026-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How explicit command-line interfaces can give AI agents useful, inspectable tools.]]></summary>
        <content type="html"><![CDATA[<p>AI agents need tools with clear inputs, observable outputs, and bounded side effects. A CLI
can provide that contract without hiding the operation behind an opaque integration layer.</p>
<!-- -->
<p>Good agent-facing CLIs still need careful schemas, validation, idempotence, useful errors,
and permissions. The command line is not automatically safe, but it is a practical surface
for making tool behavior visible to both people and agents.</p>]]></content>
        <author>
            <name>Sven Patrick Meier</name>
            <uri>https://github.com/spmse/quickli</uri>
        </author>
        <category label="General" term="General"/>
        <category label="quickli" term="quickli"/>
        <category label="cli" term="cli"/>
        <category label="ai" term="ai"/>
        <category label="agents" term="agents"/>
    </entry>
</feed>