<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Just on blog.rymcg.tech</title>
    <link>https://blog.rymcg.tech/tags/just/</link>
    <description>Recent content in Just on blog.rymcg.tech</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>Copyright © 2020-2026, EnigmaCurry</copyright>
    <lastBuildDate>Sun, 25 Jan 2026 12:00:00 -0600</lastBuildDate><atom:link href="https://blog.rymcg.tech/tags/just/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Enhanced tab completion for Justfiles with bash aliases</title>
      <link>https://blog.rymcg.tech/blog/linux/just-tab-completion/</link>
      <pubDate>Sun, 25 Jan 2026 12:00:00 -0600</pubDate>
      
      <guid>https://blog.rymcg.tech/blog/linux/just-tab-completion/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/casey/just&#34;&gt;Just&lt;/a&gt; is a command runner - a modern
take on the Makefile. You define recipes in a &lt;code&gt;Justfile&lt;/code&gt; and run them
with &lt;code&gt;just &amp;lt;recipe&amp;gt;&lt;/code&gt;. It&amp;rsquo;s become my go-to for project automation:
building code, managing VMs, running tests, whatever the project
needs.&lt;/p&gt;
&lt;p&gt;Just has built-in tab completion for recipe names, which is nice. But it
stops there. If a recipe takes arguments, you&amp;rsquo;re on your own. For a
project like
&lt;a href=&#34;https://github.com/EnigmaCurry/nixos-vm-template&#34;&gt;nixos-vm-template&lt;/a&gt;,
where recipes like &lt;code&gt;create&lt;/code&gt; take five or six arguments with valid values
that depend on the project state (profile names, existing VM names,
network modes), I wanted real completion for those arguments too.&lt;/p&gt;
&lt;p&gt;This post covers a bash completion script that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates aliases that run a Justfile from any directory&lt;/li&gt;
&lt;li&gt;Provides tab completion for recipe arguments, not just recipe names&lt;/li&gt;
&lt;li&gt;Shows hints about the next expected argument and its default value&lt;/li&gt;
&lt;li&gt;Uses a naming convention in the Justfile to define valid completions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;/h2&gt;
&lt;p&gt;Consider recipes in nixos-vm-template that take VM names as arguments:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-just&#34; data-lang=&#34;just&#34;&gt;# Justfile

status name:
    @source {{backend_script}} &amp;amp;&amp;amp; status_vm &amp;#34;{{name}}&amp;#34;

ssh target:
    @source {{backend_script}} &amp;amp;&amp;amp; ssh_vm &amp;#34;{{target}}&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When you type &lt;code&gt;just status &lt;/code&gt; and hit tab, nothing happens. Just
knows about recipe names, but it doesn&amp;rsquo;t know that the argument
is a VM name or what VMs exist.&lt;/p&gt;
&lt;p&gt;I wanted this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mrfusion-proxmox status
Next arg: name (required)

apps01      claude-dev  docker-test
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;the-solution-a-completion-convention&#34;&gt;The solution: a completion convention&lt;/h2&gt;
&lt;p&gt;The approach has two parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A bash script&lt;/strong&gt; that parses Justfile recipe signatures to understand
argument positions and names.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A naming convention&lt;/strong&gt; in the Justfile: for any argument named &lt;code&gt;foo&lt;/code&gt;,
a hidden recipe &lt;code&gt;_completion_foo&lt;/code&gt; outputs valid values.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s the convention in the Justfile:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-just&#34; data-lang=&#34;just&#34;&gt;# Justfile

# Recipes that take arguments
status name:
    @source {{backend_script}} &amp;amp;&amp;amp; status_vm &amp;#34;{{name}}&amp;#34;

ssh target:
    @source {{backend_script}} &amp;amp;&amp;amp; ssh_vm &amp;#34;{{target}}&amp;#34;

# Completion providers - one per argument type
_completion_name:
    @shopt -s nullglob; for f in machines/*; do basename $f; done

_completion_target:
    @shopt -s nullglob; for f in machines/*; do n=$(basename $f); printf &amp;#34;user@%s\nadmin@%s\n&amp;#34; &amp;#34;$n&amp;#34; &amp;#34;$n&amp;#34;; done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The completion script sees that you&amp;rsquo;re on argument index 0 of &lt;code&gt;status&lt;/code&gt;,
looks up the signature to find that argument is named &lt;code&gt;name&lt;/code&gt;, then
runs &lt;code&gt;just _completion_name&lt;/code&gt; to get the valid values.&lt;/p&gt;
&lt;h2 id=&#34;setting-up-the-alias&#34;&gt;Setting up the alias&lt;/h2&gt;
&lt;p&gt;Source the completion script in your &lt;code&gt;.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source ~/path/to/just-completion.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then define an alias with &lt;code&gt;_justfile_alias&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_justfile_alias mrfusion-proxmox &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/Justfile&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/.env-mrfusion&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The function takes three arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Alias name&lt;/strong&gt; - what you&amp;rsquo;ll type to run the recipes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Justfile path&lt;/strong&gt; - the full path to the Justfile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dotenv file&lt;/strong&gt; (optional) - an environment file to load with &lt;code&gt;-E&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now &lt;code&gt;mrfusion-proxmox&lt;/code&gt; is a command you can run from anywhere. It wraps
&lt;code&gt;just -f &amp;lt;justfile&amp;gt; -d &amp;lt;workdir&amp;gt; -E &amp;lt;dotenv&amp;gt;&lt;/code&gt; and provides full tab
completion.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;When you type &lt;code&gt;mrfusion-proxmox &lt;/code&gt; and hit tab, you get recipe names
(delegated to just&amp;rsquo;s built-in completion).&lt;/p&gt;
&lt;p&gt;When you type &lt;code&gt;mrfusion-proxmox status &lt;/code&gt; and hit tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The script sees you&amp;rsquo;re completing argument index 0 (0-indexed) of the
&lt;code&gt;status&lt;/code&gt; recipe.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It runs &lt;code&gt;just --show status&lt;/code&gt; to get the recipe signature:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;status name:
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It parses this to find that argument index 0 is &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It extracts the parameter name &lt;code&gt;name&lt;/code&gt; and runs
&lt;code&gt;just _completion_name&lt;/code&gt; to get candidates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your cursor is on an empty token, it also prints a hint:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Next arg: name (required)
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The candidates (&lt;code&gt;apps01&lt;/code&gt;, &lt;code&gt;claude-dev&lt;/code&gt;, etc.) populate the
completion list.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For arguments without a &lt;code&gt;_completion_&amp;lt;name&amp;gt;&lt;/code&gt; recipe, you still get the
hint showing the argument name and default value - you just don&amp;rsquo;t get
completion candidates.&lt;/p&gt;
&lt;h2 id=&#34;the-completion-script&#34;&gt;The completion script&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the core of the alias completion function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_justfile_alias_complete&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local alias_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;COMP_WORDS[0]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local cur&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;COMP_WORDS[COMP_CWORD]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local recipe&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;COMP_WORDS[1]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# If completing recipe name or flags, delegate to just&amp;#39;s stock completion.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;((&lt;/span&gt; COMP_CWORD &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cur&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; -* &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    JUST_JUSTFILE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$justfile&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; JUST_WORKING_DIRECTORY&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$workdir&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; _just &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$alias_name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# We are completing a positional arg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local arg_index&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$((&lt;/span&gt;COMP_CWORD &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Get the param name at this position from the recipe signature&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  local tok name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tok&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;_justfile_alias_param_token &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$alias_name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$recipe&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$arg_index&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;tok%%=*&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Try `_completion_&amp;lt;name&amp;gt;` for candidates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; -n &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local -a cands
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mapfile -t cands &amp;lt; &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;_justfile_alias_param_candidates &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$alias_name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;((&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;${#&lt;/span&gt;cands[@]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; -z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cur&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; _justfile_alias_next_arg_hint &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$alias_name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$recipe&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$arg_index&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      COMPREPLY&lt;span style=&#34;color:#f92672&#34;&gt;=(&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;compgen -W &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;cands[*]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; -- &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cur&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# No candidates: show hint when cur is empty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; -z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cur&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _justfile_alias_next_arg_hint &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$alias_name&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$recipe&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$arg_index&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    COMPREPLY&lt;span style=&#34;color:#f92672&#34;&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The hint printing uses ANSI escapes to show the hint without garbling
the current input line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_justfile_alias_hint&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  printf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\e7&amp;#39;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# save cursor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  printf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\e[J&amp;#39;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;# clear to end of screen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  printf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n\e[2K%s\n&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$1&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  printf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\e8&amp;#39;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# restore cursor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;real-world-example-nixos-vm-template&#34;&gt;Real-world example: nixos-vm-template&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s my actual setup for managing VMs on two Proxmox clusters with
different configurations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ~/.bashrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;just --completions bash&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source ~/git/vendor/enigmacurry/sway-home/config/bash/just-completion.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# MrFusion Proxmox cluster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_justfile_alias mrfusion-proxmox &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/Justfile&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/.env-mrfusion&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Flux Libvirt manager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_justfile_alias flux-libvirt &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/Justfile&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$HOME&lt;span style=&#34;color:#e6db74&#34;&gt;/git/vendor/enigmacurry/nixos-vm-template/.env-libvirt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each &lt;code&gt;.env&lt;/code&gt; file points to a different Proxmox host:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# .env-mrfusion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BACKEND&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;proxmox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PVE_HOST&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;192.168.1.100
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PVE_NODE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;mrfusion
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PVE_STORAGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;local-zfs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PVE_BRIDGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;vmbr0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I can manage VMs on either cluster from any directory:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mrfusion-proxmox status
Next arg: name (required)

apps01      claude-dev  docker-test

$ mrfusion-proxmox status apps01
Name: apps01
State: running
IP: 192.168.1.42
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The completions pull from the actual machine configs on disk, so they&amp;rsquo;re
always up to date.&lt;/p&gt;
&lt;h2 id=&#34;adding-completions-to-your-own-justfile&#34;&gt;Adding completions to your own Justfile&lt;/h2&gt;
&lt;p&gt;To add argument completion to your recipes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Identify which arguments have a known set of valid values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a hidden recipe for each, following the &lt;code&gt;_completion_&amp;lt;param&amp;gt;&lt;/code&gt;
naming convention:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-just&#34; data-lang=&#34;just&#34;&gt;# Your recipe
deploy env=&amp;#34;staging&amp;#34; service=&amp;#34;api&amp;#34;:
    ./deploy.sh {{env}} {{service}}

# Completion providers
_completion_env:
    @printf &amp;#34;dev\nstaging\nprod\n&amp;#34;

_completion_service:
    @ls services/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The completion recipe can be any command that outputs one value per
line. It runs in the Justfile&amp;rsquo;s working directory, so it can inspect
project state.&lt;/p&gt;
&lt;h2 id=&#34;gotchas&#34;&gt;Gotchas&lt;/h2&gt;
&lt;p&gt;A few things to watch out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Argument order matters.&lt;/strong&gt; The script uses positional indexing. If
your recipe is &lt;code&gt;foo a b c:&lt;/code&gt;, the first argument after &lt;code&gt;foo&lt;/code&gt; is &lt;code&gt;a&lt;/code&gt;,
the second is &lt;code&gt;b&lt;/code&gt;, etc. Named arguments (&lt;code&gt;foo a=1 b=2:&lt;/code&gt;) still work
positionally.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Spaces in values&lt;/strong&gt; are not handled. If your completion values have
spaces, you&amp;rsquo;ll need to quote them on the command line.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance.&lt;/strong&gt; Each completion invokes &lt;code&gt;just --show &amp;lt;recipe&amp;gt;&lt;/code&gt; to
parse the signature. This is fast enough for interactive use, but
you might notice a slight delay on very large Justfiles.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-full-script&#34;&gt;The full script&lt;/h2&gt;
&lt;p&gt;The complete script is available in my dotfiles:
&lt;a href=&#34;https://github.com/EnigmaCurry/sway-home/blob/master/config/bash/just-completion.sh&#34;&gt;just-completion.sh&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This setup has made working with nixos-vm-template much smoother. I can
manage VMs across multiple Proxmox clusters from any terminal, with full
tab completion for VM names when running commands like &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;ssh&lt;/code&gt;,
&lt;code&gt;upgrade&lt;/code&gt;, and &lt;code&gt;stop&lt;/code&gt;. The same pattern works for any Justfile with
discoverable argument values.&lt;/p&gt;
&lt;p&gt;The key insight is that the Justfile itself can provide completion
candidates through hidden recipes. The bash completion script just needs
to know where to look.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
