[{"data":1,"prerenderedAt":1017},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish\u002F":3,"content-directory":867},{"id":4,"title":5,"body":6,"date":853,"description":854,"difficulty":855,"draft":856,"extension":857,"meta":858,"navigation":859,"path":860,"seo":861,"stem":862,"tags":863,"updated":853,"__hash__":866},"content\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish\u002Findex.md","Installing Shell Completion for bash, zsh, fish",{"type":7,"value":8,"toc":841},"minimark",[9,19,24,127,131,138,192,207,237,243,267,269,279,292,312,329,338,369,376,378,387,416,460,484,486,489,518,527,534,537,576,582,586,589,695,729,735,739,803,807,837],[10,11,12,13,18],"p",{},"You have completion working in Python — subcommands and options complete when you drive the protocol by hand. This guide is the other half: getting that completion to fire automatically in a real shell. Each shell loads completion scripts from its own place and in its own way, so \"install completion\" means three different recipes for bash, zsh, and fish. This walks through generating the script and placing it for each shell, choosing between eager and sourced loading, and fixing the classic failure where Tab does nothing at all. Enabling completion in your code is the ",[14,15,17],"a",{"href":16},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer\u002F","sibling guide","; here we assume that already works and focus on the shell.",[20,21,23],"h2",{"id":22},"tldr","TL;DR",[25,26,27,60,77,95,109],"ul",{},[28,29,30,34,35,39,40,43,44,47,48,51,52,55,56,59],"li",{},[31,32,33],"strong",{},"Generate the script."," Click: ",[36,37,38],"code",{},"_YOURCLI_COMPLETE=bash_source yourcli"," (swap ",[36,41,42],{},"bash","→",[36,45,46],{},"zsh","\u002F",[36,49,50],{},"fish","). Typer: ",[36,53,54],{},"yourcli --show-completion",", or let ",[36,57,58],{},"yourcli --install-completion"," do it for you.",[28,61,62,65,66,69,70,73,74,76],{},[31,63,64],{},"bash:"," source the script from ",[36,67,68],{},"~\u002F.bashrc",", or drop it in a ",[36,71,72],{},"bash-completion"," directory. Requires the ",[36,75,72],{}," package.",[28,78,79,82,83,86,87,90,91,94],{},[31,80,81],{},"zsh:"," the script must be on ",[36,84,85],{},"fpath"," and ",[36,88,89],{},"compinit"," must run — set both in ",[36,92,93],{},"~\u002F.zshrc",".",[28,96,97,100,101,104,105,108],{},[31,98,99],{},"fish:"," put a file named ",[36,102,103],{},"yourcli.fish"," in ",[36,106,107],{},"~\u002F.config\u002Ffish\u002Fcompletions\u002F","; fish autoloads it, no sourcing needed.",[28,110,111,114,115,118,119,122,123,126],{},[31,112,113],{},"Nothing happens?"," Start a ",[31,116,117],{},"fresh shell",", check the command is on ",[36,120,121],{},"PATH",", and in zsh run ",[36,124,125],{},"rehash",". Ninety percent of \"completion is broken\" is one of these.",[20,128,130],{"id":129},"generate-the-completion-script","Generate the completion script",[10,132,133,134,137],{},"Every install starts by generating the shell script your CLI already knows how to emit. With Click you set the completion trigger to the ",[36,135,136],{},"*_source"," variant for the target shell and capture stdout:",[139,140,144],"pre",{"className":141,"code":142,"language":42,"meta":143,"style":143},"language-bash shiki shiki-themes github-light github-dark","$ _YOURCLI_COMPLETE=bash_source yourcli   # prints the bash completion script\n$ _YOURCLI_COMPLETE=zsh_source yourcli    # zsh version\n$ _YOURCLI_COMPLETE=fish_source yourcli   # fish version\n","",[36,145,146,166,179],{"__ignoreMap":143},[147,148,151,155,159,162],"span",{"class":149,"line":150},"line",1,[147,152,154],{"class":153},"sScJk","$",[147,156,158],{"class":157},"sZZnC"," _YOURCLI_COMPLETE=bash_source",[147,160,161],{"class":157}," yourcli",[147,163,165],{"class":164},"sJ8bj","   # prints the bash completion script\n",[147,167,169,171,174,176],{"class":149,"line":168},2,[147,170,154],{"class":153},[147,172,173],{"class":157}," _YOURCLI_COMPLETE=zsh_source",[147,175,161],{"class":157},[147,177,178],{"class":164},"    # zsh version\n",[147,180,182,184,187,189],{"class":149,"line":181},3,[147,183,154],{"class":153},[147,185,186],{"class":157}," _YOURCLI_COMPLETE=fish_source",[147,188,161],{"class":157},[147,190,191],{"class":164},"   # fish version\n",[10,193,194,195,198,199,202,203,206],{},"The variable name is derived from your console-script name: uppercased, hyphens to underscores, ",[36,196,197],{},"_COMPLETE"," appended (",[36,200,201],{},"my-tool"," → ",[36,204,205],{},"_MY_TOOL_COMPLETE","). With Typer you do not need to remember the variable — the app prints the script for you:",[139,208,210],{"className":141,"code":209,"language":42,"meta":143,"style":143},"$ yourcli --show-completion    # prints the script for your current shell\n$ yourcli --install-completion # generates AND installs it in the right place\n",[36,211,212,225],{"__ignoreMap":143},[147,213,214,216,218,222],{"class":149,"line":150},[147,215,154],{"class":153},[147,217,161],{"class":157},[147,219,221],{"class":220},"sj4cs"," --show-completion",[147,223,224],{"class":164},"    # prints the script for your current shell\n",[147,226,227,229,231,234],{"class":149,"line":168},[147,228,154],{"class":153},[147,230,161],{"class":157},[147,232,233],{"class":220}," --install-completion",[147,235,236],{"class":164}," # generates AND installs it in the right place\n",[10,238,239,242],{},[36,240,241],{},"--install-completion"," is the fast path for end users; the manual placement below is what you reach for when you want the script checked into a package, deployed system-wide, or loaded a particular way.",[10,244,245,246,249,250,253,254,258,259,262,263,266],{},"Confirm which shell you are actually in before you generate — the login shell in your terminal is not always what you assume. ",[36,247,248],{},"echo \"$0\""," or ",[36,251,252],{},"ps -p $$ -o comm="," prints the running shell, and you want the script for ",[255,256,257],"em",{},"that"," shell, not for whatever ",[36,260,261],{},"chsh"," says your default is. Generating a zsh script and sourcing it from a bash ",[36,264,265],{},".bashrc"," is a surprisingly common way to get silent, do-nothing completion.",[20,268,42],{"id":42},[10,270,271,272,274,275,278],{},"bash completion relies on the ",[36,273,72],{}," package (preinstalled on most Linux distributions; ",[36,276,277],{},"brew install bash-completion@2"," on macOS). There are two good placements.",[10,280,281,287,288,291],{},[31,282,283,284,286],{},"Sourced from ",[36,285,265],{}," (simplest, per-user)."," Generate the script once into a file and source it on shell start. Generating to a file rather than ",[36,289,290],{},"eval","-ing on every startup keeps your shell fast — you are not launching Python each time a shell opens.",[139,293,295],{"className":141,"code":294,"language":42,"meta":143,"style":143},"$ _YOURCLI_COMPLETE=bash_source yourcli > ~\u002F.yourcli-complete.bash\n",[36,296,297],{"__ignoreMap":143},[147,298,299,301,303,305,309],{"class":149,"line":150},[147,300,154],{"class":153},[147,302,158],{"class":157},[147,304,161],{"class":157},[147,306,308],{"class":307},"szBVR"," >",[147,310,311],{"class":157}," ~\u002F.yourcli-complete.bash\n",[139,313,315],{"className":141,"code":314,"language":42,"meta":143,"style":143},"# ~\u002F.bashrc\nsource ~\u002F.yourcli-complete.bash\n",[36,316,317,322],{"__ignoreMap":143},[147,318,319],{"class":149,"line":150},[147,320,321],{"class":164},"# ~\u002F.bashrc\n",[147,323,324,327],{"class":149,"line":168},[147,325,326],{"class":220},"source",[147,328,311],{"class":157},[10,330,331,334,335,337],{},[31,332,333],{},"Dropped in a completion directory (per-user or system)."," bash-completion autoloads files from its user directory, so no ",[36,336,326],{}," line is needed:",[139,339,341],{"className":141,"code":340,"language":42,"meta":143,"style":143},"$ mkdir -p ~\u002F.local\u002Fshare\u002Fbash-completion\u002Fcompletions\n$ _YOURCLI_COMPLETE=bash_source yourcli > ~\u002F.local\u002Fshare\u002Fbash-completion\u002Fcompletions\u002Fyourcli\n",[36,342,343,356],{"__ignoreMap":143},[147,344,345,347,350,353],{"class":149,"line":150},[147,346,154],{"class":153},[147,348,349],{"class":157}," mkdir",[147,351,352],{"class":220}," -p",[147,354,355],{"class":157}," ~\u002F.local\u002Fshare\u002Fbash-completion\u002Fcompletions\n",[147,357,358,360,362,364,366],{"class":149,"line":168},[147,359,154],{"class":153},[147,361,158],{"class":157},[147,363,161],{"class":157},[147,365,308],{"class":307},[147,367,368],{"class":157}," ~\u002F.local\u002Fshare\u002Fbash-completion\u002Fcompletions\u002Fyourcli\n",[10,370,371,372,375],{},"For a system-wide install (a package's postinstall step, say), write to ",[36,373,374],{},"\u002Fusr\u002Fshare\u002Fbash-completion\u002Fcompletions\u002Fyourcli"," instead. Either way, open a new shell to pick it up.",[20,377,46],{"id":46},[10,379,380,381,383,384,386],{},"zsh needs two things to be true: your completion script must be on ",[36,382,85],{},", and the completion system (",[36,385,89],{},") must have been initialised. Miss either and Tab stays silent. The robust per-user setup:",[139,388,390],{"className":141,"code":389,"language":42,"meta":143,"style":143},"$ mkdir -p ~\u002F.zfunc\n$ _YOURCLI_COMPLETE=zsh_source yourcli > ~\u002F.zfunc\u002F_yourcli\n",[36,391,392,403],{"__ignoreMap":143},[147,393,394,396,398,400],{"class":149,"line":150},[147,395,154],{"class":153},[147,397,349],{"class":157},[147,399,352],{"class":220},[147,401,402],{"class":157}," ~\u002F.zfunc\n",[147,404,405,407,409,411,413],{"class":149,"line":168},[147,406,154],{"class":153},[147,408,173],{"class":157},[147,410,161],{"class":157},[147,412,308],{"class":307},[147,414,415],{"class":157}," ~\u002F.zfunc\u002F_yourcli\n",[139,417,419],{"className":141,"code":418,"language":42,"meta":143,"style":143},"# ~\u002F.zshrc  — order matters: fpath BEFORE compinit\nfpath=(~\u002F.zfunc $fpath)\nautoload -Uz compinit && compinit\n",[36,420,421,426,443],{"__ignoreMap":143},[147,422,423],{"class":149,"line":150},[147,424,425],{"class":164},"# ~\u002F.zshrc  — order matters: fpath BEFORE compinit\n",[147,427,428,431,434,437,440],{"class":149,"line":168},[147,429,85],{"class":430},"sVt8B",[147,432,433],{"class":307},"=",[147,435,436],{"class":430},"(",[147,438,439],{"class":157},"~\u002F.zfunc",[147,441,442],{"class":430}," $fpath)\n",[147,444,445,448,451,454,457],{"class":149,"line":181},[147,446,447],{"class":220},"autoload",[147,449,450],{"class":220}," -Uz",[147,452,453],{"class":157}," compinit",[147,455,456],{"class":430}," && ",[147,458,459],{"class":153},"compinit\n",[10,461,462,463,466,467,469,470,473,474,476,477,480,481,94],{},"The leading underscore in the filename (",[36,464,465],{},"_yourcli",") is a zsh convention for completion functions — keep it. If you use a framework like Oh My Zsh that already calls ",[36,468,89],{},", just make sure your ",[36,471,472],{},"fpath="," line runs before it; putting the ",[36,475,85],{}," addition near the top of ",[36,478,479],{},".zshrc"," is the safe choice. After editing, start a new shell or run ",[36,482,483],{},"exec zsh",[20,485,50],{"id":50},[10,487,488],{},"fish is the easiest of the three: it autoloads any file in its completions directory, matched by command name. No sourcing, no init call.",[139,490,492],{"className":141,"code":491,"language":42,"meta":143,"style":143},"$ mkdir -p ~\u002F.config\u002Ffish\u002Fcompletions\n$ _YOURCLI_COMPLETE=fish_source yourcli > ~\u002F.config\u002Ffish\u002Fcompletions\u002Fyourcli.fish\n",[36,493,494,505],{"__ignoreMap":143},[147,495,496,498,500,502],{"class":149,"line":150},[147,497,154],{"class":153},[147,499,349],{"class":157},[147,501,352],{"class":220},[147,503,504],{"class":157}," ~\u002F.config\u002Ffish\u002Fcompletions\n",[147,506,507,509,511,513,515],{"class":149,"line":168},[147,508,154],{"class":153},[147,510,186],{"class":157},[147,512,161],{"class":157},[147,514,308],{"class":307},[147,516,517],{"class":157}," ~\u002F.config\u002Ffish\u002Fcompletions\u002Fyourcli.fish\n",[10,519,520,521,523,524,94],{},"The filename must match the command (",[36,522,103],{},"). fish picks it up in new shells automatically, and often in the current one too because it loads completions lazily on first use. For a system-wide install, write to ",[36,525,526],{},"\u002Fusr\u002Fshare\u002Ffish\u002Fvendor_completions.d\u002Fyourcli.fish",[20,528,530,531,533],{"id":529},"eager-eval-vs-sourced-from-file","Eager (",[36,532,290],{},") vs sourced-from-file",[10,535,536],{},"You will see two styles in the wild and it is worth knowing the trade-off.",[25,538,539,563],{},[28,540,541,547,548,551,552,554,555,558,559,94],{},[31,542,543,544,546],{},"Eager \u002F ",[36,545,290],{}," on every startup"," — ",[36,549,550],{},"eval \"$(_YOURCLI_COMPLETE=bash_source yourcli)\""," directly in ",[36,553,265],{},". Always current, because it regenerates the script each time. The cost: it launches your Python program on ",[31,556,557],{},"every"," new shell, which adds startup latency you will feel if the tool is heavy. This is where a fast CLI pays off — see ",[14,560,562],{"href":561},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002F","CLI startup performance and lazy loading",[28,564,565,568,569,571,572,575],{},[31,566,567],{},"Sourced from a generated file"," — generate once to a file, then ",[36,570,326],{}," or autoload that file. Zero Python at shell start, so it is the better default. The catch: the script is a snapshot. If you add subcommands or rename options, the ",[255,573,574],{},"shape"," of completion can go stale until you regenerate. (Dynamic value callbacks still run your program at Tab-time, so those stay fresh regardless.)",[10,577,578,579,581],{},"For most users, generate to a file. Reserve ",[36,580,290],{}," for a machine where you are actively developing the CLI and want the completion structure to track your code without regenerating.",[20,583,585],{"id":584},"troubleshooting-completion-never-fires","Troubleshooting: completion never fires",[10,587,588],{},"When Tab does nothing, work down this list — the fixes are almost always mundane.",[590,591,592,605,638,659,672,683,689],"ol",{},[28,593,594,597,598,601,602,604],{},[31,595,596],{},"You did not start a fresh shell."," Completion registration happens at shell startup. After installing, open a new terminal or run ",[36,599,600],{},"exec bash"," \u002F ",[36,603,483],{}," \u002F start a new fish. This is the single most common cause.",[28,606,607,612,613,616,617,619,620,622,623,626,627,630,631,633,634,94],{},[31,608,609,610,94],{},"The command is not on ",[36,611,121],{}," The generated script invokes ",[36,614,615],{},"yourcli"," to compute candidates. If ",[36,618,615],{}," is not on the ",[36,621,121],{}," of the shell where you press Tab, nothing comes back. Confirm with ",[36,624,625],{},"command -v yourcli",". Tools installed with pipx or ",[36,628,629],{},"uv tool"," put a launcher on ",[36,632,121],{}," for you — see ",[14,635,637],{"href":636},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002F","installing and distributing CLIs with pipx",[28,639,640,643,644,646,647,649,650,652,653,656,657,94],{},[31,641,642],{},"zsh needs a rehash."," zsh caches the commands on ",[36,645,121],{},". Right after installing a new CLI, zsh may not \"see\" it until you run ",[36,648,125],{}," (or open a new shell). If the completion function itself is not found, re-check that ",[36,651,85],{}," is set ",[255,654,655],{},"before"," ",[36,658,89],{},[28,660,661,664,665,668,669,94],{},[31,662,663],{},"Stale zsh completion cache."," zsh caches completion metadata in ",[36,666,667],{},"~\u002F.zcompdump",". After changing a completion script, remove it and reinitialise: ",[36,670,671],{},"rm -f ~\u002F.zcompdump && compinit",[28,673,674,677,678,680,681,76],{},[31,675,676],{},"bash-completion is not installed."," The generated bash script depends on the ",[36,679,72],{}," runtime. On a minimal system it may be missing; install the ",[36,682,72],{},[28,684,685,688],{},[31,686,687],{},"It works in one shell but not another."," Each shell loads a different script from a different place, so a working bash setup tells you nothing about zsh. Install completion once per shell you actually use, and test each in its own fresh session.",[28,690,691,694],{},[31,692,693],{},"The Python side is actually broken."," Rule this out fast by driving the protocol directly, with no shell in the loop:",[139,696,698],{"className":141,"code":697,"language":42,"meta":143,"style":143},"$ _YOURCLI_COMPLETE=bash_complete COMP_WORDS=\"yourcli \" COMP_CWORD=1 yourcli\nplain,deploy\nplain,status\n",[36,699,700,719,724],{"__ignoreMap":143},[147,701,702,704,707,710,713,716],{"class":149,"line":150},[147,703,154],{"class":153},[147,705,706],{"class":157}," _YOURCLI_COMPLETE=bash_complete",[147,708,709],{"class":157}," COMP_WORDS=\"yourcli \"",[147,711,712],{"class":157}," COMP_CWORD=",[147,714,715],{"class":220},"1",[147,717,718],{"class":157}," yourcli\n",[147,720,721],{"class":149,"line":168},[147,722,723],{"class":153},"plain,deploy\n",[147,725,726],{"class":149,"line":181},[147,727,728],{"class":153},"plain,status\n",[10,730,731,732,94],{},"If that prints candidates, your program is fine and the problem is placement or loading. If it prints nothing, fix completion in your code first — that is the ",[14,733,734],{"href":16},"enabling guide",[20,736,738],{"id":737},"production-notes","Production notes",[25,740,741,750,760,780,790],{},[28,742,743,746,747,749],{},[31,744,745],{},"Ship the script, don't make users generate it."," When you package the CLI, include a generated completion file per shell and document one-line install steps, or lean on Typer's ",[36,748,241],{},". Regenerate the files as part of your release so their structure tracks the code.",[28,751,752,755,756,94],{},[31,753,754],{},"The script name must match the console-script name."," Both the completion trigger variable and the filename derive from the installed command. Renaming the entry point invalidates a shipped script; keep them in step with your ",[14,757,759],{"href":758},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points\u002F","entry points",[28,761,762,765,766,769,770,773,774,773,776,779],{},[31,763,764],{},"System vs user directories."," System paths (",[36,767,768],{},"\u002Fusr\u002Fshare\u002F...",") suit OS packages; user paths (",[36,771,772],{},"~\u002F.local\u002Fshare\u002F...",", ",[36,775,439],{},[36,777,778],{},"~\u002F.config\u002Ffish\u002Fcompletions",") suit pip\u002Fpipx installs where you cannot write to system locations.",[28,781,782,785,786,789],{},[31,783,784],{},"Test in a clean shell."," Completion bugs hide behind an already-configured environment. Verify in ",[36,787,788],{},"env -i bash --noprofile --norc"," (then source only your script) or a fresh container so you catch missing dependencies your own dotfiles paper over.",[28,791,792,795,796,601,799,802],{},[31,793,794],{},"Pin versions."," The generated script format is tied to the framework — pin ",[36,797,798],{},"click>=8.1",[36,800,801],{},"typer>=0.12"," and regenerate scripts when you upgrade.",[20,804,806],{"id":805},"related","Related",[25,808,809,815,822,831],{},[28,810,811,814],{},[14,812,813],{"href":16},"Enabling tab completion in Click and Typer"," — the sibling guide: make completion work in your Python code first.",[28,816,817,821],{},[14,818,820],{"href":819},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002F","Shell completion for Python CLIs"," — the overview tying the Python and shell sides together.",[28,823,824,827,828,830],{},[14,825,826],{"href":636},"Installing and distributing CLIs with pipx"," — get the command onto users' ",[36,829,121],{}," so completion can invoke it.",[28,832,833,836],{},[14,834,835],{"href":758},"Best practices for Python CLI entry points"," — the console-script name the completion script keys off.",[838,839,840],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":143,"searchDepth":168,"depth":168,"links":842},[843,844,845,846,847,848,850,851,852],{"id":22,"depth":168,"text":23},{"id":129,"depth":168,"text":130},{"id":42,"depth":168,"text":42},{"id":46,"depth":168,"text":46},{"id":50,"depth":168,"text":50},{"id":529,"depth":168,"text":849},"Eager (eval) vs sourced-from-file",{"id":584,"depth":168,"text":585},{"id":737,"depth":168,"text":738},{"id":805,"depth":168,"text":806},"2026-07-05","Generate and install shell completion scripts for a Python CLI on bash, zsh, and fish, load them per-user or system-wide, and fix completions that never fire.","intermediate",false,"md",{},true,"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish",{"title":5,"description":854},"advanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish\u002Findex",[864,42,46,50,865],"completion","cli","VyJ1G3bIb4w8PvluGHQfUoPgxrsovTEUQsty1NPpeO8",[868,871,874,877,880,883,886,889,892,895,898,901,904,907,910,911,914,917,920,922,925,928,931,934,937,940,943,946,948,951,954,957,960,963,966,969,972,975,978,981,984,987,990,993,996,999,1002,1005,1008,1011,1014],{"path":869,"title":870},"\u002Fabout","About Python CLI Toolcraft",{"path":872,"title":873},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":875,"title":876},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":878,"title":879},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":881,"title":882},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":884,"title":885},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":887,"title":888},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":890,"title":891},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":893,"title":894},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps","Loading YAML configs safely in CLI apps",{"path":896,"title":897},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":899,"title":900},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fadding-progress-bars-and-spinners-to-python-clis","Progress Bars and Spinners for Python CLIs",{"path":902,"title":903},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":905,"title":906},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer","Enabling Tab Completion in Click and Typer",{"path":908,"title":909},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":860,"title":5},{"path":912,"title":913},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":915,"title":916},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":918,"title":919},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":47,"title":921},"Python CLI Toolcraft",{"path":923,"title":924},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":926,"title":927},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":929,"title":930},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":932,"title":933},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":935,"title":936},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":938,"title":939},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":941,"title":942},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":944,"title":945},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":947,"title":835},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points",{"path":949,"title":950},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fhow-to-structure-a-large-python-cli-project","Structuring a Large Python CLI Project",{"path":952,"title":953},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":955,"title":956},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":958,"title":959},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click","Building a CLI with subcommands in Click",{"path":961,"title":962},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":964,"title":965},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":967,"title":968},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":970,"title":971},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":973,"title":974},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":976,"title":977},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":979,"title":980},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":982,"title":983},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":985,"title":986},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":988,"title":989},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":991,"title":992},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":994,"title":995},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":997,"title":998},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1000,"title":1001},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos","Setting up pre-commit for Python CLI repos",{"path":1003,"title":1004},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1006,"title":1007},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-init-vs-poetry-init-for-cli-tools","uv init vs poetry init for CLI tools",{"path":1009,"title":1010},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis","uv tool install vs pipx for CLIs",{"path":1012,"title":1013},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1015,"title":1016},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",1783281867196]