Easily turn Nushell modules into cross-shell CLI tools
library nu nushell
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial module

+145
+73
mod.nu
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 MatrixFurry <matrix@matrixfurry.com> 3 + 4 + const template: path = path self ./template.nu 5 + 6 + export def install [ 7 + src: path # Module source path 8 + prefix?: path = ~/.local # installation prefix 9 + --force (-f) # Overwrite existing files 10 + --install-module (-i) # Install module in Nushell library directory 11 + --bin-name (-b): string # Override final binary name 12 + --module-name (-m): string # Override module name 13 + --print-usage # [EXPERIMENTAL] Print command usage when executing --help or --list 14 + ] { 15 + let src = $src | path expand 16 + let prefix = $prefix | path expand 17 + let mod_name = $module_name | default ($src | path basename) 18 + let installed_mod_path = $prefix | path join "share" $mod_name "module" $mod_name 19 + let bin_name = $bin_name | default $mod_name 20 + let installed_bin_path = $prefix | path join "bin" $bin_name 21 + 22 + if $force { 23 + rm -rf $installed_mod_path 24 + rm -f $installed_bin_path 25 + } else if ($installed_mod_path | path exists) or ($installed_bin_path | path exists) { 26 + error make -u { 27 + msg: "It looks like this tool is already installed, or there are conflicting files installed" 28 + help: "If you're attempting to update or re-install the tool, use --force" 29 + } 30 + } 31 + 32 + # Module 33 + mkdir ($installed_mod_path | path dirname) 34 + cp -r $src $installed_mod_path 35 + 36 + if $install_module { 37 + let nu_lib_dir = $NU_LIB_DIRS | first 38 + if $force {rm -f ($nu_lib_dir | path join $mod_name)} 39 + ln -s $installed_mod_path $nu_lib_dir 40 + } 41 + 42 + # Create & install binary 43 + mkdir ($installed_bin_path | path dirname) 44 + 45 + open $template --raw 46 + | str replace "@MODULE_PATH@" $installed_mod_path 47 + | str replace "@PRINT_USAGE@" ($print_usage | into string) 48 + | save --progress $installed_bin_path 49 + 50 + chmod +x $installed_bin_path 51 + } 52 + 53 + export def uninstall [ 54 + name: string # CLI tool to uninstall 55 + prefix?: path = ~/.local # installation prefix 56 + --bin-name (-b): string # Override binary name 57 + --remove-module (-m) # Remove module in Nushell library directory 58 + ] { 59 + let prefix = $prefix | path expand 60 + let mod_name = $name 61 + let installed_mod_path = $prefix | path join "share" $mod_name "module" $mod_name 62 + let bin_name = $bin_name | default $name 63 + let installed_bin_path = $prefix | path join "bin" $bin_name 64 + 65 + rm -rf $installed_mod_path 66 + rm -f $installed_bin_path 67 + 68 + if $remove_module { 69 + let nu_lib_dir = $NU_LIB_DIRS | first 70 + rm -f ($nu_lib_dir | path join $mod_name) 71 + } 72 + } 73 +
+72
template.nu
··· 1 + #!/usr/bin/env nu 2 + # SPDX-License-Identifier: AGPL-3.0-only 3 + # Copyright (c) 2026 MatrixFurry <matrix@matrixfurry.com> 4 + 5 + const module_path = @MODULE_PATH@ 6 + const module_name = $module_path | path basename 7 + const print_usage = @PRINT_USAGE@ 8 + 9 + use std log 10 + use $module_path 11 + 12 + def main --wrapped [ 13 + # TODO: --choose (-c) # Interactively choose a function 14 + --help (-h) 15 + --list (-l) # List available commands 16 + --interactive (-i) # Enter interactive shell with the module loaded 17 + ...cmd 18 + ] { 19 + if $interactive { 20 + exec nu -e $"use ($module_path); print '(ansi yellow)Module loaded.(ansi reset)'" 21 + } else if $help or $list or ($cmd | length) == 0 { 22 + print-functions 23 + } else { 24 + let cmd_str = $cmd | str join ' ' 25 + let function = get-functions | where $it starts-with $cmd_str | first 26 + 27 + if ($function | is-empty) { 28 + log error $"Unknown command: `($cmd_str)`" 29 + print-functions 30 + } 31 + 32 + exec nu -c $"use ($module_path); ($module_name) ($cmd_str)" 33 + } 34 + } 35 + 36 + def get-functions [] { 37 + scope modules 38 + | where name == $module_name 39 + | get 0.commands.name 40 + } 41 + 42 + def print-functions [] { 43 + get-functions 44 + | where not ($it starts-with "_") 45 + | each {|function| 46 + let usage = if $print_usage { 47 + let help = help $module_name $function | lines 48 + {( 49 + $help 50 + | get ( 51 + ($help | enumerate | where item == $"(ansi green)Usage(ansi reset):").0.index + 1 52 + ) 53 + | str trim 54 + )} 55 + } else {{}} 56 + 57 + let short_description = $help 58 + | first 59 + | if $in == $"(ansi green)Usage(ansi reset):" { 60 + null 61 + } else { 62 + $in 63 + } 64 + 65 + { 66 + command: $"(ansi cyan)($function)(ansi reset)" 67 + description: $"(ansi lp)($short_description)(ansi reset)" 68 + } merge $usage 69 + } 70 + | table --index false --theme rounded 71 + } 72 +