diff --git a/.gitignore b/.gitignore
index b343c6b2757c76d96ce39f08c5d2e62e0fe30fc5..8077771daa9fc897842910eedac0a0756f99a6a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ STM32F429zi_Discovery.xml
 ._*
 .DS_Store
 build
+build-host
 cmake-build-*
 *.sublime-workspace
 *.sublime-project
diff --git a/cmake/sbs.cmake b/cmake/sbs.cmake
index 7cd5a66c2c8cf0578c4ecaa613577d82498a1a14..dc56212db454cba1f02a9945ec405ff1ceb543e2 100644
--- a/cmake/sbs.cmake
+++ b/cmake/sbs.cmake
@@ -28,15 +28,6 @@ cmake_path(GET CMAKE_CURRENT_LIST_DIR PARENT_PATH SBS_BASE)
 list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
 include(boardcore)
 
-# Command to print all the available boards used by the sbs script
-string(REPLACE ";" "\\n" BOARDS_STR "${MIOSIX_BOARDS};${BOARDCORE_BOARDS}")
-add_custom_target(
-    help-boards
-    COMMAND printf ${BOARDS_STR}
-    COMMENT "All boards available:"
-    VERBATIM
-)
-
 # Function to link the Boardcore library to the target
 function(sbs_target TARGET OPT_BOARD)
     if(NOT OPT_BOARD)
diff --git a/sbs b/sbs
index f867e996023a6212e05367413329cbe40ce40a03..bdf753882f2a1461e070f5e59d8ce3251052b6e8 100755
--- a/sbs
+++ b/sbs
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
-# Copyright (c) 2021 Skyward Experimental Rocketry
-# Author: Damiano Amatruda
+# Copyright (c) 2024 Skyward Experimental Rocketry
+# Author: Davide Basso
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -21,38 +21,235 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
 
-print_banner() {
-    cat <<EOF
-+---------------------------------------------------------------+
-|   ____  _                                _                    |
-|  / ___|| | ___   ___      ____ _ _ __ __| |                   |
-|  \\___ \\| |/ / | | \\ \\ /\\ / / _\` | '__/ _\` |                   |
-|   ___) |   <| |_| |\\ V  V / (_| | | | (_| |                   |
-|  |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_|  \\__,_|                   |
-|   ____       |___/    _   ____            _                   |
-|  | __ ) _   _(_) | __| | / ___| _   _ ___| |_ ___ _ __ ___    |
-|  |  _ \\| | | | | |/ _\` | \\___ \\| | | / __| __/ _ \\ '_ \` _ \\   |
-|  | |_) | |_| | | | (_| |  ___) | |_| \\__ \\ ||  __/ | | | | |  |
-|  |____/ \\__,_|_|_|\\__,_| |____/ \\__, |___/\\__\\___|_| |_| |_|  |
-+----------------------------------|___/-------------------v3.3-+
-EOF
+################################################################################
+##############################      Constants     ##############################
+################################################################################
+
+# Terminal colors
+TTY_RESET="\033[0m"
+TTY_BOLD="\033[1m"
+TTY_FOUND="\033[38;5;200m"
+TTY_SUCCESS="\033[38;5;40m"
+TTY_ERROR="\033[38;5;160m"
+
+TTY_LOGO_1="\033[38;5;200m"
+TTY_LOGO_2="\033[38;5;164m"
+TTY_LOGO_3="\033[38;5;93m"
+TTY_LOGO_4="\033[38;5;57m"
+TTY_LOGO_5="\033[38;5;27m"
+
+# Error message
+ERR="\n"$TTY_ERROR""$TTY_BOLD"ERROR"$TTY_RESET""
+
+# Filenames/Dirnames
+CMAKE_FILENAME="CMakeCache.txt"
+CTEST_FILENAME="CTestTestfile.cmake"
+DEBUG_FILENAME=".sbs_debug"
+VERBOSE_FILENAME=".sbs_verbose"
+BUILD_DEFAULT_DIRNAME="build"
+BUILD_HOST_DIRNAME="build-host"
+
+################################################################################
+##############################    Global States   ##############################
+################################################################################
+
+sbs_base=
+source_dir=
+build_default_dir=
+build_host_dir=
+toolchain_file=
+
+found_cmake=false
+found_miosixgpp=false
+found_ccache=false
+found_ninja=false
+found_python=false
+found_cppcheck=false
+found_clangtidy=false
+found_clangformat=false
+found_stflash=false
+found_stlink=false
+
+################################################################################
+##############################       Banner       ##############################
+################################################################################
+
+banner="
+$TTY_LOGO_1              _____ ____ _____                                  
+$TTY_LOGO_2 ___________ / ___// __ ) ___/  ________________________________
+$TTY_LOGO_3 __________  \__ \/ __  \__ \  _________________________________
+$TTY_LOGO_4 _________  ___/ / /_/ /__/ / ____________________________v4.0__
+$TTY_LOGO_5           /____/_____/____/                                        
+$TTY_RESET                                                                  
+"
+
+print_configuration() {
+	step "Dependencies"
+
+	yf=""$TTY_SUCCESS""$TTY_BOLD"Yes"$TTY_RESET""
+	nf=""$TTY_ERROR""$TTY_BOLD"No"$TTY_RESET""
+
+	printf "cmake:               "; [ "$found_cmake" = true       ] && printf "$yf\n" || printf "$nf\n"
+	printf "arm-miosix-eabi-g++: "; [ "$found_miosixgpp" = true   ] && printf "$yf\n" || printf "$nf\n"
+	printf "ccache:              "; [ "$found_ccache" = true      ] && printf "$yf\n" || printf "$nf\n"
+	printf "ninja:               "; [ "$found_ninja" = true       ] && printf "$yf\n" || printf "$nf\n"
+	printf "python:              "; [ "$found_python" = true      ] && printf "$yf\n" || printf "$nf\n"
+	printf "cppcheck:            "; [ "$found_cppcheck" = true    ] && printf "$yf\n" || printf "$nf\n"
+	printf "clang-tidy:          "; [ "$found_clangtidy" = true   ] && printf "$yf\n" || printf "$nf\n"
+	printf "clang-format:        "; [ "$found_clangformat" = true ] && printf "$yf\n" || printf "$nf\n"
+
+	printf "flasher:             "; 
+	if [ "$found_stflash" = true ]; then
+		printf ""$TTY_FOUND""$TTY_BOLD"st-flash"$TTY_RESET"\n"
+	elif [ "$found_stlink" = true ]; then
+		printf ""$TTY_FOUND""$TTY_BOLD"st-link"$TTY_RESET"\n"
+	else
+		printf "$nf\n"
+	fi
+
+	[ "$found_cmake" = true ]     || { printf ""$ERR": CMake must be installed\n"; return 1; }
+    [ "$found_miosixgpp" = true ] || { printf ""$ERR": arm-miosix-eabi-g++ must be installed\n"; return 1; }
 }
 
-ohai() {
-    printf "\n${TTY_BLUE}==>${TTY_RESET}${TTY_BOLD} %s${TTY_RESET}\n" "$@"
+################################################################################
+##############################         CLI        ##############################
+################################################################################
+
+# Filter all the arguments that are flags
+# The syntax is: filter_flags "arg1" "arg2" ... "argN"
+# Returns a list of flags in the form "--flag=value" or "--flag"
+filter_flags() {
+    flags=()
+    prev_is_flag=false
+
+    for arg in "$@"; do
+        if [[ "$arg" == -* ]]; then
+            flags+=("$arg")
+            prev_is_flag=true
+        else
+            if [ "$prev_is_flag" = true ]; then
+                # if the previous argument was a flag, then this is a flag value
+                flags[-1]="${flags[-1]}=$arg"
+            fi
+
+            prev_is_flag=false
+        fi
+    done
+
+    echo "${flags[@]}"
 }
 
-init_dirs() {
-    sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)"
-    source_dir="$PWD"
-    build_default_dir="$source_dir/$BUILD_DEFAULT_DIRNAME"
-    build_host_dir="$source_dir/$BUILD_HOST_DIRNAME"
-    toolchain_file="$sbs_base/libs/miosix-kernel/miosix/cmake/toolchain.cmake"
+
+# Filter all the arguments that are not flags
+filter_args() {
+    args=()
+    prev_is_flag=false
+
+    for arg in "$@"; do
+        if [[ "$arg" == -* ]]; then
+            prev_is_flag=true
+        elif [ "$prev_is_flag" = true ]; then
+            prev_is_flag=false
+        else
+            args+=("$arg")
+        fi
+    done
+
+    echo "${args[@]}"
 }
 
-find_deps() {
-    ohai "Find dependencies"
-    
+# Checks that no unknown flags are passed
+# The syntax is: cmd_flags "known_flags" "flags"
+cmd_flags() {    
+    known_flags=("${!1}")
+    flags=("${!2}")
+
+    for flag in "${flags[@]}"; do
+        # ignore ''
+        if [ -z "$flag" ]; then
+            continue
+        fi
+        
+        found=false
+        for known_flag in "${known_flags[@]}"; do
+            # if the know_flag is of type --flag=, allow flags with value
+            if [[ "$known_flag" == *= ]]; then
+                if [[ "$flag" == "$known_flag"* ]]; then
+                    found=true
+                    break
+                fi
+            else
+                if [ "$flag" == "$known_flag" ]; then
+                    found=true
+                    break
+                fi
+            fi
+        done
+
+        if [ "$found" = false ]; then
+            printf ""$ERR": Unknown flag $flag\n"
+            return 1
+        fi
+    done
+}
+
+# Checks that no more than a certain number of arguments are passed
+# The syntax is: cmd_args "max_args" "arg1" "arg2" ... "argN"
+cmd_args() {
+    max_args=$1
+    shift
+    args=("${!1}")
+    if [ "${#args[@]}" -gt "$max_args" ]; then
+        printf ""$ERR": Too many arguments\n"
+        return 1
+    fi
+}
+
+# Checks if a flag exists in the list of flags
+# The syntax is: flag_exists "flag" "flag1" "flag2" ... "flagN"
+flag_exists() {
+    flag=$1
+    shift
+    flags=("${!1}")
+
+    if [[ " ${flags[@]} " =~ " ${flag} " ]]; then
+        echo "true"
+    else
+        echo "false"
+    fi
+}
+
+# Get the value of a flag
+# The syntax is: get_flag_value "possible_flags" "flags"
+get_flag_value() {
+    possible_flags=("${!1}")
+    flags=("${!2}")
+
+    for flag in "${flags[@]}"; do
+        for possible_flag in "${possible_flags[@]}"; do
+            if [[ "$flag" == "$possible_flag"* ]]; then
+                # strip the value from the flag
+                echo "${flag#*=}"
+                return
+            fi
+        done
+    done
+}
+
+# Print a step message
+step() {
+	printf "\n"$TTY_LOGO_5""$TTY_BOLD"$1"$TTY_RESET"\n"
+	# Print - for every letter in the pre string
+	for (( i=0; i<${#1}; i++ )); do printf -- "-"; done;
+	printf "\n";
+}
+
+################################################################################
+##############################      Internals     ##############################
+################################################################################
+
+# Find all the dependencies
+find_deps() {    
     command -v cmake               > /dev/null 2>&1 && found_cmake=true
     command -v arm-miosix-eabi-g++ > /dev/null 2>&1 && found_miosixgpp=true
     command -v ccache              > /dev/null 2>&1 && found_ccache=true
@@ -63,57 +260,51 @@ find_deps() {
     command -v clang-format        > /dev/null 2>&1 && found_clangformat=true
     command -v st-flash            > /dev/null 2>&1 && found_stflash=true
     command -v ST-LINK_CLI.exe     > /dev/null 2>&1 && found_stlink=true
-    
-    printf "Found CMake: ";               [ "$found_cmake" = true ]       && echo "yes" || echo "no"
-    printf "Found arm-miosix-eabi-g++: "; [ "$found_miosixgpp" = true ]   && echo "yes" || echo "no"
-    printf "Found Ccache: ";              [ "$found_ccache" = true ]      && echo "yes" || echo "no"
-    printf "Found Ninja: ";               [ "$found_ninja" = true ]       && echo "yes" || echo "no"
-    printf "Found Python: ";              [ "$found_python" = true ]      && echo "yes" || echo "no"
-    printf "Found Cppcheck: ";            [ "$found_cppcheck" = true ]    && echo "yes" || echo "no"
-    printf "Found clang-tidy: ";          [ "$found_clangtidy" = true ]   && echo "yes" || echo "no"
-    printf "Found clang-format: ";        [ "$found_clangformat" = true ] && echo "yes" || echo "no"
-    printf "Found flasher: ";             [ "$found_stflash" = true ]     && echo "st-flash" \
-    || { [ "$found_stlink" = true ] && echo "st-link" || echo "no"; }
-    
-    [ "$found_cmake" = true ]     || { echo "Error: CMake must be installed"; return 1; }
-    [ "$found_miosixgpp" = true ] || { echo "Error: arm-miosix-eabi-g++ must be installed"; return 1; }
 }
 
-# Workaround: Disable tests in excluded subdirectories
-# See: https://gitlab.kitware.com/cmake/cmake/-/issues/20212
+# Initialize all the directories
+init_dirs() {
+    sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)"
+    source_dir="$PWD"
+    build_default_dir="$source_dir/$BUILD_DEFAULT_DIRNAME"
+    build_host_dir="$source_dir/$BUILD_HOST_DIRNAME"
+    toolchain_file="$sbs_base/libs/miosix-kernel/miosix/cmake/toolchain.cmake"
+}
+
+# Get the build options for cmake
+# The syntax is: get_build_opts "opts" "jobs"
+get_build_opts() {
+    build_opts=()
+    jobs=$2
+
+    if [ -n "$jobs" ]; then
+        echo "- Building with $jobs jobs"
+        build_opts+=("-j $jobs")
+    fi
+}
+
+# workaround: disable tests in excluded subdirectories
+# see: https://gitlab.kitware.com/cmake/cmake/-/issues/20212
 cmake_disable_excluded_tests() {
-    declare build_dir="$1"
-    
+    build_dir="$build_host_dir"
+
     [ ! -f "$build_dir/$CTEST_FILENAME" ] || sed -i.bak 's/^subdirs/# subdirs/' "$build_dir/$CTEST_FILENAME"
 }
 
-configure() {
-    declare build_dir="$1"
-    
-    ohai "Configure"
-    
-    [ -f "$toolchain_file" ] || { echo "Error: CMake Toolchain File for Miosix was not found"; return 1; }
-    
-    declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)
-    defs+=(-DCMAKE_C_FLAGS=-fdiagnostics-color=always -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always)
-    [ "$config_host" = false ]   && defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" -DBUILD_TESTING=OFF)
-    [ "$found_ccache" = true ]   && defs+=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
-    [ "$config_debug" = true ]   && defs+=(-DCMAKE_BUILD_TYPE=Debug) || defs+=(-DCMAKE_BUILD_TYPE=Release)
-    [ "$config_verbose" = true ] && defs+=(-DCMAKE_VERBOSE_MAKEFILE=ON)
-    
-    declare gen
-    [ "$found_ninja" = true ] && gen=-GNinja || gen=-G"Unix Makefiles"
-    
-    cmake -B"$build_dir" "${defs[@]}" "$gen" "$source_dir" || return
-    
-    { [ "$config_debug" = true ]   && touch "$build_dir/$DEBUG_FILENAME";   } || rm -f "$build_dir/$DEBUG_FILENAME"
-    { [ "$config_verbose" = true ] && touch "$build_dir/$VERBOSE_FILENAME"; } || rm -f "$build_dir/$VERBOSE_FILENAME"
-}
+################################################################################
+##############################     Subcommands    ##############################
+################################################################################
 
+# Check if the project is configured
+# The syntax is: check_configured "config_debug" "config_verbose" "config_host"
 check_configured() {
-    declare build_dir="$1"
+    build_dir="$1"
+    config_debug="$2"
+    config_verbose="$3"
+    config_host="$4"
+
+    to_reconfigure=false
     
-    declare to_reconfigure=false
     if [ ! -d "$build_dir" ]; then
         to_reconfigure=true
         elif [ ! -f "$build_dir/$CMAKE_FILENAME" ]; then
@@ -129,318 +320,436 @@ check_configured() {
     fi
     
     if [ "$to_reconfigure" = true ]; then
-        configure "$build_dir"
+        step "Configure"
+
+        printf "Reconfiguring project with the following options:\n"
+        printf "  - Debug:   %s\n" "$config_debug"
+        printf "  - Verbose: %s\n" "$config_verbose"
+        printf "  - Host:    %s\n" "$config_host"
+
+        [ -f "$toolchain_file" ] || { printf ""$ERR": CMake Toolchain File for Miosix was not found\n"; return 1; }
+        
+        declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)
+        defs+=(-DCMAKE_C_FLAGS=-fdiagnostics-color=always -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always)
+        [ "$config_host" = false ]   && defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" -DBUILD_TESTING=OFF)
+        [ "$found_ccache" = true ]   && defs+=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
+        [ "$config_debug" = true ]   && defs+=(-DCMAKE_BUILD_TYPE=Debug) || defs+=(-DCMAKE_BUILD_TYPE=Release)
+        [ "$config_verbose" = true ] && defs+=(-DCMAKE_VERBOSE_MAKEFILE=ON)
+        
+        declare gen
+        [ "$found_ninja" = true ] && gen=-GNinja || gen=-G"Unix Makefiles"
+        
+        cmake -B "$build_dir" "${defs[@]}" "$gen" "$source_dir" || return
+        
+        { [ "$config_debug" = true ]   && touch "$build_dir/$DEBUG_FILENAME";   } || rm -f "$build_dir/$DEBUG_FILENAME"
+        { [ "$config_verbose" = true ] && touch "$build_dir/$VERBOSE_FILENAME"; } || rm -f "$build_dir/$VERBOSE_FILENAME"
     fi
 }
 
-build() {
-    declare build_dir="$1"
-    declare target="$2"
-    
-    check_configured "$build_dir" || return
+lint_copyright() {
+    step "Lint copyright"
+
+    "$sbs_base/scripts/linter.py" --copyright "$source_dir/src"
+}
+
+lint_find() {
+    step "Lint find"
+
+    "$sbs_base/scripts/linter.py" --find "$source_dir/src"
+}
+
+lint_clangtidy() {
+    build_dir="$1"
     
-    ohai "Build"
+    step "Lint clang-tidy"
+
+    config_debug=false
+    config_verbose=false
+
+    defs=(--extra-arg=-D_MIOSIX=1 --extra-arg=-D_MIOSIX_GCC_PATCH_MINOR=1 \
+        --extra-arg=-D_MIOSIX_GCC_PATCH_MAJOR=3 --extra-arg=-D__LINT__)
+    IFS=$'\n' read -rd '' -a incs < \
+        <(arm-miosix-eabi-g++ -E -Wp,-v -xc++ /dev/null 2>&1 \
+        | sed -n "s/^ /--extra-arg=-isystem/p")
     
-    declare -a opts
-    get_build_opts opts
+    declare opts=()
+    [ "$to_edit" = true ] && opts+=(--fix-notes --fix-errors)
     
-    cmake --build "$build_dir" "${opts[@]}" --target "$target"
+    find "$source_dir/src" \
+        -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
+        -exec clang-tidy --header-filter=".*" -p="$build_dir" "${defs[@]}" \
+        "${incs[@]}" "${opts[@]}" {} \;
 }
 
-build_all() {
-    declare build_dir="$build_default_dir"
-    
-    build "$build_dir" all
+lint_cppcheck() {
+    step "Lint cppcheck"
+
+    echo "Running cppcheck..."
+
+    cppcheck --language=c++ --std=c++14 --enable=all --inline-suppr \
+        --suppress=unmatchedSuppression --suppress=unusedFunction \
+        --suppress=missingInclude --error-exitcode=1 -q \
+        "$source_dir/src"
 }
 
-clean() {
-    declare build_desc="$1"
-    declare build_dir="$2"
-    
-    ohai "Clean ($build_desc)"
+lint_clangformat() {
+    step "Lint clang-format"
+
+    echo "Running clang-format..."
+
+    opts=(--style=file --Werror --dry-run)
+
+    find "$source_dir/src" \
+    -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
+    -exec clang-format "${opts[@]}" {} \;
+}
+
+################################################################################
+##############################   Native Commands  ##############################
+################################################################################
+
+build() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    jobs_flag=("-j" "--jobs")
+    known_flags=("-d" "-v" "-j=" "--debug" "--verbose" "--jobs=")
+
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 1 args[@] || return
+
+    jobs=$(get_flag_value jobs_flag[@] flags[@])
+
+    [ "$(flag_exists "-d" flags[@])" = true ] || [ "$(flag_exists "--debug"   flags[@])" = true ] && config_debug=true   || config_debug=false
+    [ "$(flag_exists "-v" flags[@])" = true ] || [ "$(flag_exists "--verbose" flags[@])" = true ] && config_verbose=true || config_verbose=false
+    config_host=false
+
+    build_dir="$build_default_dir"
+
+    target=${args[0]:-all}
+
+    check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return
     
-    if [ -f "$build_dir/$CMAKE_FILENAME" ]; then
-        declare -a opts
-        get_build_opts opts
-        
-        cmake --build "$build_dir" "${opts[@]}" --target clean
-    fi
+    step "Build"
+
+    declare opts
+    get_build_opts opts $jobs
     
-    echo "Removing build folder..."
-    rm -rf "$build_dir"
+    cmake --build "$build_dir" "${opts[@]}" --target "$target"
 }
 
-clean_all() {
-    clean "Default" "$build_default_dir"
-    clean "Host" "$build_host_dir"
+clean() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    known_flags=()
+
+    step "Clean"
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 0 args[@] || return
+
+    echo "Cleaning build directories..."
+    rm -rf "$build_default_dir"
+    rm -rf "$build_host_dir"
 }
 
 flash() {
-    declare target="$1"
-    declare build_dir="$build_default_dir"
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    jobs_flag=("-j" "--jobs")
+    known_flags=("-d" "-v" "-r" "-j=" "--debug" "--verbose" "--reset" "--jobs=")
+
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 1 args[@] || return
+
+    build_dir="$build_default_dir"
+
+    jobs=$(get_flag_value jobs_flag[@] flags[@])
+
+    [ "$(flag_exists "-d" flags[@])" = true ] || [ "$(flag_exists "--debug"   flags[@])" = true ] && config_debug=true   || config_debug=false
+    [ "$(flag_exists "-v" flags[@])" = true ] || [ "$(flag_exists "--verbose" flags[@])" = true ] && config_verbose=true || config_verbose=false
+    [ "$(flag_exists "-r" flags[@])" = true ] || [ "$(flag_exists "--reset"   flags[@])" = true ] && reset=true          || reset=false
+    config_host=false
+
+    target=${args[0]}
+
+    if [ -z "$target" ]; then
+        printf ""$ERR": No target specified\n"
+        return 1
+    fi
+
+    check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return
     
-    build "$build_dir" "$target" || return
+    # build the target
+    step "Build"
+
+    declare opts
+    get_build_opts opts $jobs
     
-    ohai "Flash"
+    cmake --build "$build_dir" "${opts[@]}" --target "$target"
     
-    [ -f "$build_dir/$target.bin" ] || { echo "Error: target '$target' is not flashable"; return 1; }
+    # check if the target is flashable
+    [ -f "$build_dir/$target.bin" ] || { printf ""$ERR": target '$target' is not flashable"; return 1; }
     
+    # flash the target
+    step "Flash"
+
+    flash_opts=()
+    [ "$reset" = true ] && flash_opts+=("--connect-under-reset")
+
     if [ "$found_stflash" = true ]; then
-        st-flash --reset write "$build_dir/$target.bin" 0x8000000
-        elif [ "$found_stlink" = true ]; then
+        st-flash --reset "${flash_opts[@]}" write "$build_dir/$target.bin" 0x8000000 
+    elif [ "$found_stlink" = true ]; then
         ST-LINK_CLI.exe -P "$build_dir/$target.bin" 0x8000000 -V -Rst
     else
-        echo "Error: No flashing software found!"
+        printf ""$ERR": No flashing software found!\n"
         return 1
     fi
 }
 
-run_tests() {
-    declare target="$1"
-    declare build_dir="$build_host_dir"
-    
+test() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    jobs_flag=("-j" "--jobs")
+    known_flags=("-j=" "--jobs=")
+
+    cmd_flags know_flags[@] flags[@] || return
+    cmd_args 1 args[@] || return
+
+    config_debug=true
+    config_verbose=false
     config_host=true
+    build_dir="$build_host_dir"
+
+    jobs=$(get_flag_value jobs_flag[@] flags[@])
+
+    target=${args[0]}
+
+    if [ -z "$target" ]; then
+        printf ""$ERR": No target specified\n"
+        return 1
+    fi
+
+    check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return
+
+    # build the target
+    step "Build"
+
+    declare opts
+    get_build_opts opts $jobs
     
-    build "$build_dir" "$target" || return
+    cmake --build "$build_dir" "${opts[@]}" --target "$target"
     
-    ohai "Test"
+    # run the tests
+    step "Test"
     
     cmake_disable_excluded_tests "$build_dir"
     ( cd "$build_dir" || return; ctest )
 }
 
 list() {
-    declare build_dir="$build_default_dir"
-    
-    check_configured "$build_dir" || return
-    
-    ohai "List targets"
-    
-    declare -a opts
-    get_build_opts opts
-    
-    echo "[1/1] All SBS targets available:"
-    cmake --build "$build_dir" "${opts[@]}" --target help \
-    | grep -o '^[^/]*\.bin' | cut -f 1 -d '.'
-}
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
 
-boards() {
-    declare build_dir="$build_default_dir"
-    
-    check_configured "$build_dir" || return
-    
-    ohai "List boards"
-    
-    declare -a opts
-    get_build_opts opts
-    
-    cmake --build "$build_dir" "${opts[@]}" --target help-boards
-}
+    known_flags=()
 
-lint_copyright() {
-    ohai "Lint (Copyright)"
-    
-    "$sbs_base/scripts/linter.py" --copyright "$source_dir/src"
-}
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 1 args[@] || return
 
-lint_find() {
-    ohai "Lint (Find)"
-    
-    "$sbs_base/scripts/linter.py" --find "$source_dir/src"
-}
+    config_debug=false
+    config_verbose=false
+    config_host=false
+    build_dir="$build_default_dir"
 
-lint_clangtidy() {
-    declare build_dir="$1"
-    
-    check_configured "$build_dir" || return
-    
-    ohai "Lint (clang-tidy)"
-    
-    defs=(--extra-arg=-D_MIOSIX=1 --extra-arg=-D_MIOSIX_GCC_PATCH_MINOR=1 \
-    --extra-arg=-D_MIOSIX_GCC_PATCH_MAJOR=3 --extra-arg=-D__LINT__)
-    IFS=$'\n' read -rd '' -a incs < \
-    <(arm-miosix-eabi-g++ -E -Wp,-v -xc++ /dev/null 2>&1 \
-    | sed -n "s/^ /--extra-arg=-isystem/p")
-    
-    declare opts=()
-    [ "$to_edit" = true ] && opts+=(--fix-notes --fix-errors)
-    
-    find "$source_dir/src" \
-    -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
-    -exec clang-tidy --header-filter=".*" -p="$build_dir" "${defs[@]}" \
-    "${incs[@]}" "${opts[@]}" {} \;
-}
+    check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return
 
-lint_cppcheck() {
-    ohai "Lint (Cppcheck)"
-    
-    declare -a opts=()
-    [ -n "$jobs" ] && opts+=("-j $jobs")
-    
-    cppcheck --language=c++ --std=c++14 --enable=all --inline-suppr \
-    --suppress=unmatchedSuppression --suppress=unusedFunction \
-    --suppress=missingInclude --error-exitcode=1 -q "${opts[@]}" \
-    "$source_dir/src"
-}
+    list_type=${args[0]}
 
-lint_clangformat() {
-    declare to_edit="$1"
-    
-    ohai "Lint (clang-format)"
-    
-    declare -a opts=(--style=file --Werror)
-    [ "$to_edit" = true ] && opts+=(-i) || opts+=(--dry-run)
-    
-    find "$source_dir/src" \
-    -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
-    -exec clang-format "${opts[@]}" {} \;
+    if [ -z "$list_type" ]; then
+        printf ""$ERR": No list type specified\n"
+        return 1
+    fi
+
+    if [ "$list_type" = "targets" ]; then
+        cmake --build "$build_dir" --target help \
+            | grep -o '^[^/]*\.bin' | cut -f 1 -d '.'
+    elif [ "$list_type" = "boards" ]; then
+        cmake --build "$build_dir" --target help | awk -F '[-:]' '/^boardcore/ {print $2}'
+    else
+        printf ""$ERR": Unknown list type $list_type\n"
+        return 1
+    fi
 }
 
 lint() {
-    declare to_edit="$1"
-    
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    known_flags=()
+
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 0 args[@] || return
+
     if [ "$found_python" = true ]; then
         lint_copyright
         lint_find
+    else
+        echo "Python not found, skipping copyright and find..."
     fi
-    
-    if [ "$found_clangtidy" = true ] && [ "$lint_clangtidy" = true ]; then
-        lint_clangtidy "$build_default_dir"
-    fi
-    
+
+    # Disable clang-tidy for now, as too many false positives
+    # if [ "$found_clangtidy" = true ]; then
+    #     lint_clangtidy "$build_default_dir"
+    # else
+    #     echo "Clang-tidy not found, skipping clang-tidy..."
+    # fi
+
     if [ "$found_cppcheck" = true ]; then
         lint_cppcheck
+    else
+        echo "Cppcheck not found, skipping cppcheck..."
     fi
-    
+
     if [ "$found_clangformat" = true ]; then
-        lint_clangformat "$to_edit"
+        lint_clangformat
+    else
+        echo "Clang-format not found, skipping clang-format..."
     fi
 }
 
-set_debug() {
-    config_debug=true
-}
+format() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    known_flags=()
 
-set_verbose() {
-    config_verbose=true
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 0 args[@] || return
+
+    step "Format"
+
+    echo "Running clang-format..."
+
+    opts=(--style=file --Werror -i)
+
+    find "$source_dir/src" \
+    -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
+    -exec clang-format "${opts[@]}" {} \;
 }
 
-set_jobs() {
-    jobs="$1"
+help() {
+    echo "Usage: sbs (command) <args> [options] "
+    echo ""
+    echo "Commands:"
+    echo " build     - Build the specified target"
+    echo "           -> <target=all> [-d | --debug] [-v | --verbose] [-j | --jobs=<jobs>]"
+    echo " flash     - Flash the specified target"
+    echo "           -> <target> [-d | --debug] [-v | --verbose] [-r | --reset] [-j | --jobs=<jobs>]"
+    echo " test      - Run the tests for the specified target"
+    echo "           -> <target> [-j | --jobs=<jobs>]"
+    echo " list      - List the available targets or boards"
+    echo "           -> <targets | boards>"
+    echo " clean     - Clean the build directory"
+    echo " lint      - Lint the source code"
+    echo " format    - Format the source code"
+    echo " install   - Install autocomplete"
+    echo " uninstall - Uninstall autocomplete"
+    echo ""
+    echo "Options:"
+    echo " -d, --debug:   Build in debug mode"
+    echo " -v, --verbose: Build in verbose mode"
+    echo " -r, --reset:   Reset the target before flashing"
+    echo " -j, --jobs:    Number of jobs to run in parallel"
+    echo ""
 }
 
-get_build_opts() {
-    declare -n build_opts=$1
-    [ -n "$jobs" ] && build_opts=("-j $jobs")
+################################################################################
+##############################   Python Commands  ##############################
+################################################################################
+
+
+install() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    known_flags=()
+
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 0 args[@] || return
+
+    step "Install autocomplete"
+
+    if [ "$found_python" = false ]; then
+        printf ""$ERR": Python is required to install autocomplete\n"
+        return 1
+    fi
+
+    echo "Retrieving targets..."
+
+    targets=$(list "targets")
+
+    # split the targets on newlines
+    IFS=$'\n' read -rd '' -a targets <<< "$targets"
+
+    echo "Found ${#targets[@]} targets"
+
+    python "$sbs_base/scripts/autocomplete.py" "--install" "${targets[@]}"
 }
 
-set_clangtidy() {
-    lint_clangtidy=true
+uninstall() {
+    IFS=' ' read -r -a flags <<< "$(filter_flags "$@")"
+    IFS=' ' read -r -a args <<< "$(filter_args "$@")"
+
+    known_flags=()
+
+    cmd_flags known_flags[@] flags[@] || return
+    cmd_args 0 args[@] || return
+
+    step "Uninstall autocomplete"
+
+    if [ "$found_python" = false ]; then
+        printf ""$ERR": Python is required to uninstall autocomplete\n"
+        return 1
+    fi
+
+    python "$sbs_base/scripts/autocomplete.py" "--uninstall"
 }
 
-usage() {
-    echo
-    cat <<EOF
-Usage: $(basename "$0") [OPTIONS]
-
-OPTIONS:
-  General Options:
-    -h, --help            Show this help message and exit
-    -j JOBS, --jobs JOBS  Build or lint in parallel using a specific number of jobs
-    -l, --list            List all targets available
-    -r, --boards          List all boards available
-
-  Build Options:
-    -b TARGET, --build TARGET
-                          Build a specific target
-    -f TARGET, --flash TARGET
-                          Build and flash a specific target
-    -t TEST, --test TEST  Build a specific test natively and run it
-    -c, --clean           Clean the working tree
-    -u, --configure       Force configure and do not build
-    -d, --debug           Enable debug
-    -v, --verbose         Print a verbose output
-
-  Lint Options:
-    -n, --lint            Lint the code
-    -e, --edit            Lint and edit the code
-    --clang-tidy          Lint using also clang-tidy
-EOF
+################################################################################
+##############################        Main        ##############################
+################################################################################
+
+init_no_output() {
+    find_deps
+    init_dirs
 }
 
-CMAKE_FILENAME="CMakeCache.txt"
-CTEST_FILENAME="CTestTestfile.cmake"
-DEBUG_FILENAME=".sbs_debug"
-VERBOSE_FILENAME=".sbs_verbose"
-BUILD_DEFAULT_DIRNAME="build"
-BUILD_HOST_DIRNAME="cmake-build-host"
-TTY_BLUE="\033[34m"
-TTY_BOLD="\033[1m"
-TTY_RESET="\033[0m"
+init() {
+    printf "$banner"
 
-sbs_base=
-source_dir=
-build_default_dir=
-build_host_dir=
-toolchain_file=
-found_cmake=false
-found_miosixgpp=false
-found_ccache=false
-found_ninja=false
-found_python=false
-found_cppcheck=false
-found_clangtidy=false
-found_clangformat=false
-found_stflash=false
-found_stlink=false
-config_debug=false
-config_verbose=false
-config_host=false
-jobs=
-lint_clangtidy=false
+    find_deps
+    init_dirs
+    print_configuration
+}
 
-print_banner
-init_dirs
 
 for arg in "$@"; do
-    shift
-    case "$arg" in
-        --boards)     set -- "$@" "-r";;
-        --build)      set -- "$@" "-b";;
-        --clang-tidy) set_clangtidy;;
-        --clean)      set -- "$@" "-c";;
-        --configure)  set -- "$@" "-u";;
-        --debug)      set -- "$@" "-d";;
-        --edit)       set -- "$@" "-e";;
-        --flash)      set -- "$@" "-f";;
-        --help)       set -- "$@" "-h";;
-        --jobs)       set -- "$@" "-j";;
-        --lint)       set -- "$@" "-n";;
-        --list)       set -- "$@" "-l";;
-        --test)       set -- "$@" "-t";;
-        --verbose)    set -- "$@" "-v";;
-        *)            set -- "$@" "$arg"
-    esac
-done
-
-while getopts b:cdef:hj:lnrt:uv opt; do
-    case "$opt" in
-        b) find_deps && build "$build_default_dir" "$OPTARG"; exit;;
-        c) find_deps && clean_all; exit;;
-        d) set_debug;;
-        e) find_deps && lint true; exit;;
-        f) find_deps && flash "$OPTARG"; exit;;
-        h) usage; exit 0;;
-        j) set_jobs "$OPTARG";;
-        l) find_deps && list; exit;;
-        n) find_deps && lint false; exit;;
-        r) find_deps && boards; exit;;
-        t) find_deps && run_tests "$OPTARG"; exit;;
-        u) find_deps && configure "$build_default_dir"; exit;;
-        v) set_verbose;;
-        ?) usage; exit 2;;
+    case $arg in
+        build)     init; build "${@:2}"; exit ;;
+        clean)     init; clean "${@:2}"; exit ;;
+        flash)     init; flash "${@:2}"; exit ;;
+        test)      init; test  "${@:2}"; exit ;;
+        lint)      init; lint  "${@:2}"; exit ;;
+        format)    init; format; exit ;;
+        install)   init; install; exit ;;
+        uninstall) init; uninstall; exit ;;
+        list)      init_no_output; list  "${@:2}"; exit ;;
+        *)         help; exit ;;
     esac
 done
-shift $((OPTIND - 1))
 
-find_deps && build_all
+if [ "$#" -eq 0 ]; then
+    help
+fi
\ No newline at end of file
diff --git a/scripts/autocomplete.py b/scripts/autocomplete.py
new file mode 100755
index 0000000000000000000000000000000000000000..d90181a01fd4ab7344cd68e3578c5c83e9fa037f
--- /dev/null
+++ b/scripts/autocomplete.py
@@ -0,0 +1,293 @@
+# Copyright (c) 2024 Skyward Experimental Rocketry
+# Author: Davide Basso
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import os
+import sys
+import inspect
+import platform
+from shutil import which
+from pathlib import Path
+
+LINUX_BASH_COMPLETION_FOLDER = "/etc/bash_completion.d"
+SBS_AUTOCOMPLETE_FOLDER = "sbs"
+SBS_AUTOCOMPLETE_MAIN = "sbs-autocomplete.sh"
+COMMANDS = [
+    "build",
+    "clean",
+    "flash",
+    "list",
+    "test",
+    "lint",
+    "format",
+    "install",
+    "uninstall",
+]
+COMMANDS_WITH_TARGET = ["build", "flash", "test"]
+
+
+def strip_extension(file: str):
+    return os.path.splitext(file)[0]
+
+
+def write_autocomplete_script(file, project_name: str, targets: list[str]):
+    bash = inspect.cleandoc(
+        f"""#!/bin/bash
+        # Skyward Build System autocompletion script
+        # This script is automatically generated by sbs
+        # Do not edit manually
+
+        _sbs_{project_name}()
+        {{
+            local cur prev starting cmds cmds_with_target targets
+            COMPREPLY=()
+            cur="${{COMP_WORDS[COMP_CWORD]}}"
+            prev="${{COMP_WORDS[COMP_CWORD-1]}}"
+            starting="${{COMP_WORDS[0]}}"
+
+            cmds=({' '.join([f'"{c}"' for c in COMMANDS])})
+            cmds_with_target=({' '.join([f'"{c}"' for c in COMMANDS_WITH_TARGET])})
+            targets=({' '.join([f'"{t}"' for t in targets])})
+            
+            if [[ ${{prev}} == "./sbs" ]] ; then
+                COMPREPLY=( $(compgen -W "{' '.join(COMMANDS)}" -- "${{cur}}") )
+            elif [[ ${{starting}} == "./sbs" && " ${{cmds_with_target[*]}} " =~ [[:space:]]${{prev}}[[:space:]] ]]; then
+                COMPREPLY=( $(compgen -W "{' '.join(targets)}" -- "${{cur}}") )
+            fi
+
+            return 0
+        }}
+        """
+    )
+    file.write(bash)
+
+
+def write_base_script(file):
+    bash = inspect.cleandoc(
+        f"""#!/bin/bash
+        # Skyward Build System autocompletion script
+        # This script is automatically generated by sbs
+        # Do not edit manually
+
+        _sbs__base()
+        {{
+            local cur prev starting cmds
+            COMPREPLY=()
+            cur="${{COMP_WORDS[COMP_CWORD]}}"
+            prev="${{COMP_WORDS[COMP_CWORD-1]}}"
+            starting="${{COMP_WORDS[1]}}"
+
+            cmds=({' '.join([f'"{c}"' for c in COMMANDS])})
+            
+            if [[ ${{prev}} == sbs ]] ; then
+                COMPREPLY=( $(compgen -W "{' '.join(COMMANDS)}" -- "${{cur}}") )
+            fi
+
+            return 0
+        }}
+        """
+    )
+    file.write(bash)
+
+
+def write_main_script(file, folder: str, projects_scripts: list[str]):
+    newline = "\n        "
+    bash = inspect.cleandoc(
+        f"""#!/bin/bash
+        # Skyward Build System autocompletion script
+        # This script is automatically generated by sbs
+        # Do not edit manually
+
+        source "{folder}/__base.sh"
+        # Source into all the project scripts
+        {newline.join([f'source "{folder}/{s}"' for s in projects_scripts])}
+
+        stringContain() {{ case $2 in *$1* ) return 0;; *) return 1;; esac ;}}
+
+        _sbs()
+        {{
+            local projects
+            projects=({' '.join([f'"{strip_extension(s)}"' for s in projects_scripts])})
+
+            # Check if current working directory is under any project folder
+            for project in ${{projects[*]}};
+            do
+                if stringContain "$project" "$PWD"; then
+                    _sbs_$project
+                    return 0
+                fi
+            done
+
+            _sbs__base
+
+            return 0
+        }}
+
+        complete -F _sbs ./sbs
+        """
+    )
+    file.write(bash)
+
+
+def find_git_bash_completion_folder():
+    # Git bash stores bash completion files inside \mingw64\share\git\completion
+    # We first get the path of the git executable, and use it to find the completion folder
+    git_path = which("git")
+    mingw64_path = os.path.dirname(os.path.dirname(git_path))
+    return os.path.join(mingw64_path, "share", "git", "completion")
+
+
+def add_autocomplete_script_to_bashrc(path: str):
+    home = Path.home()
+    bashrc_path = os.path.join(home, ".bashrc")
+
+    fixed_path = path.replace("\\", "/")
+    cmd = f'# Skyward Build System autocompletion: \nsource "{fixed_path}"'
+    # Check if the script is already sourced
+    with open(bashrc_path, "r") as f:
+        bashrc = f.read()
+    if cmd in bashrc:
+        print(f"Autocomplete script already sourced in {bashrc_path}")
+        return
+    # Add the script to bashrc
+    with open(bashrc_path, "a") as f:
+        f.write(f"\n{cmd}")
+    print(f"Added autocomplete script to {bashrc_path}")
+
+
+def find_folders():
+    if platform.system() == "Linux":
+        if not os.path.exists(LINUX_BASH_COMPLETION_FOLDER):
+            raise OSError(
+                f"Could not find bash completion folder {LINUX_BASH_COMPLETION_FOLDER};"
+            )
+        autocomplete_folder = LINUX_BASH_COMPLETION_FOLDER
+        print(f"Detected Linux platform, using {autocomplete_folder}")
+    elif platform.system() == "Windows":
+        autocomplete_folder = find_git_bash_completion_folder()
+        print(f"Detected Windows platform, using {autocomplete_folder}")
+        add_autocomplete_script_to_bashrc(
+            os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_MAIN)
+        )
+    else:
+        raise OSError(f"Unsupported platform {platform.system()}")
+
+    project_name = os.path.basename(os.getcwd())
+    sbs_scripts_folder = os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_FOLDER)
+    main_file_path = os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_MAIN)
+    autocomplete_file_path = os.path.join(sbs_scripts_folder, project_name + ".sh")
+    base_script_path = os.path.join(sbs_scripts_folder, "__base.sh")
+
+    return (
+        project_name,
+        sbs_scripts_folder,
+        main_file_path,
+        autocomplete_file_path,
+        base_script_path,
+    )
+
+
+def install_autocomplete(targets: list[str]):
+    print(f"\nSetting up autocomplete for current project...")
+
+    (
+        project_name,
+        sbs_scripts_folder,
+        main_file_path,
+        autocomplete_file_path,
+        base_script_path,
+    ) = find_folders()
+
+    # Create paths for autocomplete scripts
+    os.makedirs(sbs_scripts_folder, exist_ok=True)
+
+    # Generate autocomplete script
+    if os.path.exists(autocomplete_file_path):
+        print(f"Removing old autocomplete script {autocomplete_file_path}...")
+        os.remove(autocomplete_file_path)
+
+    with open(autocomplete_file_path, "w+") as f:
+        print(f"Writing autocomplete script to {autocomplete_file_path}...")
+        write_autocomplete_script(f, project_name, targets)
+
+    projects_scripts = os.listdir(sbs_scripts_folder)
+    if "__base.sh" in projects_scripts:
+        projects_scripts.remove("__base.sh")
+
+    # Write base script
+    if os.path.exists(base_script_path):
+        print(f"Removing old base script {base_script_path}...")
+        os.remove(base_script_path)
+
+    with open(base_script_path, "w+") as f:
+        print(f"Writing base script to {base_script_path}...")
+        write_base_script(f)
+
+    # Generate main script
+    if os.path.exists(main_file_path):
+        os.remove(main_file_path)
+
+    with open(main_file_path, "w+") as f:
+        print(f"Writing main script to {main_file_path}...")
+        write_main_script(f, sbs_scripts_folder, projects_scripts)
+
+    print(f"Done, restart your shell to see the changes\n")
+
+
+def uninstall_autocomplete():
+    print("Uninstalling autocomplete script...")
+
+    (
+        project_name,
+        sbs_scripts_folder,
+        main_file_path,
+        autocomplete_file_path,
+        base_script_path,
+    ) = find_folders()
+
+    if os.path.exists(autocomplete_file_path):
+        print(f"Removing autocomplete script {autocomplete_file_path}...")
+        os.remove(autocomplete_file_path)
+    else:
+        print(f"Could not find autocomplete script {autocomplete_file_path}")
+
+    if os.path.exists(base_script_path):
+        print(f"Removing base script {base_script_path}...")
+        os.remove(base_script_path)
+    else:
+        print(f"Could not find base script {base_script_path}")
+
+    if os.path.exists(main_file_path):
+        print(f"Removing main script {main_file_path}...")
+        os.remove(main_file_path)
+    else:
+        print(f"Could not find main script {main_file_path}")
+
+    print("Done, restart your shell to see the changes\n")
+
+
+if __name__ == "__main__":
+    type = sys.argv[1]
+    if type == "--install":
+        install_autocomplete(sys.argv[2:])
+    elif type == "--uninstall":
+        uninstall_autocomplete()
+    else:
+        raise ValueError(f"Unknown argument {type}")
diff --git a/scripts/linter.py b/scripts/linter.py
index 1beb95593ea5117b5b7996362ce5e6f4e7e9e82a..c31d95e7a5bbe8cc71023246a4a792a9deea6846 100755
--- a/scripts/linter.py
+++ b/scripts/linter.py
@@ -34,50 +34,69 @@ from os import pathconf, walk
 from os.path import join
 
 # Copyright template for C++ code files and headers
-CPP_TEMPLATE = r'\/\* Copyright \(c\) 20\d\d(?:-20\d\d)? Skyward Experimental Rocketry\n' + \
-    r' \* (Authors?): (.+)\n' + \
-    r' \*\n' + \
-    r' \* Permission is hereby granted, free of charge, to any person obtaining a copy\n' + \
-    r' \* of this software and associated documentation files \(the "Software"\), to deal\n' + \
-    r' \* in the Software without restriction, including without limitation the rights\n' + \
-    r' \* to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\n' + \
-    r' \* copies of the Software, and to permit persons to whom the Software is\n' + \
-    r' \* furnished to do so, subject to the following conditions:\n' + \
-    r' \*\n' + \
-    r' \* The above copyright notice and this permission notice shall be included in\n' + \
-    r' \* all copies or substantial portions of the Software.\n' + \
-    r' \*\n' + \
-    r' \* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n' + \
-    r' \* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n' + \
-    r' \* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n' + \
-    r' \* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n' + \
-    r' \* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n' + \
-    r' \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n' + \
-    r' \* THE SOFTWARE.\n' + \
-    r' \*\/'
-AUTHOR_DELIMITER = ','
+CPP_TEMPLATE = (
+    r"\/\* Copyright \(c\) 20\d\d(?:-20\d\d)? Skyward Experimental Rocketry\n"
+    + r" \* (Authors?): (.+)\n"
+    + r" \*\n"
+    + r" \* Permission is hereby granted, free of charge, to any person obtaining a copy\n"
+    + r' \* of this software and associated documentation files \(the "Software"\), to deal\n'
+    + r" \* in the Software without restriction, including without limitation the rights\n"
+    + r" \* to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\n"
+    + r" \* copies of the Software, and to permit persons to whom the Software is\n"
+    + r" \* furnished to do so, subject to the following conditions:\n"
+    + r" \*\n"
+    + r" \* The above copyright notice and this permission notice shall be included in\n"
+    + r" \* all copies or substantial portions of the Software.\n"
+    + r" \*\n"
+    + r' \* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n'
+    + r" \* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+    + r" \* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
+    + r" \* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+    + r" \* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
+    + r" \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
+    + r" \* THE SOFTWARE.\n"
+    + r" \*\/"
+)
+AUTHOR_DELIMITER = ","
 
 
 def config_cmd_parser():
     parser = ArgumentParser(
-        description='The linter script offers a lot of functionalities but it does not modify any file. If no option is specified everything will be checked.',
-        formatter_class=RawTextHelpFormatter)
-    parser.add_argument('directory', nargs='?',
-                        help='Directory where to search files')
+        description="The linter script offers a lot of functionalities but it does not modify any file. If no option is specified everything will be checked.",
+        formatter_class=RawTextHelpFormatter,
+    )
+    parser.add_argument("directory", nargs="?", help="Directory where to search files")
     parser.add_argument(
-        '--copyright', dest='copyright',
-        action='store_true', help='Checks for the right copyright notice in all .h and .cpp files')
+        "--copyright",
+        dest="copyright",
+        action="store_true",
+        help="Checks for the right copyright notice in all .h and .cpp files",
+    )
     parser.add_argument(
-        '--format', dest='format',
-        action='store_true', help='Checks if all .h and .cpp files respects the clang format specified in .clang-format file')
+        "--format",
+        dest="format",
+        action="store_true",
+        help="Checks if all .h and .cpp files respects the clang format specified in .clang-format file",
+    )
     parser.add_argument(
-        '--find', dest='find',
-        action='store_true', help='Checks if some .h or .cpp files have printfs, asserts or some string used in them')
+        "--find",
+        dest="find",
+        action="store_true",
+        help="Checks if some .h or .cpp files have printfs, asserts or some string used in them",
+    )
     parser.add_argument(
-        '--cppcheck', dest='cppcheck',
-        action='store_true', help='Runs cppcheck for static code analysis')
-    parser.add_argument('-q', '--quiet', dest='quiet',
-                        action='store_true', help='Output only essential messages')
+        "--cppcheck",
+        dest="cppcheck",
+        action="store_true",
+        help="Runs cppcheck for static code analysis",
+    )
+    parser.add_argument(
+        "-q",
+        "--quiet",
+        dest="quiet",
+        action="store_true",
+        help="Output only essential messages",
+    )
     return parser
 
 
@@ -92,22 +111,22 @@ def print_banner():
     print(r"+------------------------------+")
 
 
-class Colors():
-    BLACK = '\033[30m'
-    RED = '\033[31m'
-    GREEN = '\033[32m'
-    YELLOW = '\033[33m'
-    BLUE = '\033[34m'
-    MAGENTA = '\033[35m'
-    CYAN = '\033[36m'
-    WHITE = '\033[37m'
-    UNDERLINE = '\033[4m'
-    RESET = '\033[0m'
+class Colors:
+    BLACK = "\033[30m"
+    RED = "\033[31m"
+    GREEN = "\033[32m"
+    YELLOW = "\033[33m"
+    BLUE = "\033[34m"
+    MAGENTA = "\033[35m"
+    CYAN = "\033[36m"
+    WHITE = "\033[37m"
+    UNDERLINE = "\033[4m"
+    RESET = "\033[0m"
 
 
 # Checks for the right copyright notice in all .h and .cpp files
 def check_copyright(directory):
-    print(Colors.GREEN + 'Copyright check' + Colors.RESET)
+    print(Colors.GREEN + "Copyright check" + Colors.RESET)
 
     # Statistics
     totalCheckdFilesCounter = 0
@@ -117,7 +136,7 @@ def check_copyright(directory):
 
     # Walk through the directory and check each file
     for dirpath, dirnames, filenames in walk(directory):
-        for filename in [f for f in filenames if f.endswith(('.cpp', '.h'))]:
+        for filename in [f for f in filenames if f.endswith((".cpp", ".h"))]:
             totalCheckdFilesCounter += 1
 
             # Prepare the complete filepath
@@ -130,19 +149,29 @@ def check_copyright(directory):
                     filesWithErrorsCounter += 1
 
                     # The file's copyright notice does not match the template!
-                    print(Colors.YELLOW + 'Wrong copyright notice in file {0}'.format(
-                        currentFilepath) + Colors.RESET)
+                    print(
+                        Colors.YELLOW
+                        + "Wrong copyright notice in file {0}".format(currentFilepath)
+                        + Colors.RESET
+                    )
                 else:
-                    fileAuthors = [a.strip()
-                                   for a in match.group(2).split(AUTHOR_DELIMITER)]
+                    fileAuthors = [
+                        a.strip() for a in match.group(2).split(AUTHOR_DELIMITER)
+                    ]
 
                     # Check the number of authors against 'Author' or `Authors`
-                    if len(fileAuthors) == 1 and match.group(1)[-1] == 's':
-                        print('\'Authors\' should to be changed to \'Author\' in {0}'.format(
-                            currentFilepath))
-                    if len(fileAuthors) > 1 and match.group(1)[-1] != 's':
-                        print('\'Author\' should to be changed to \'Authors\' in {0}'.format(
-                            currentFilepath))
+                    if len(fileAuthors) == 1 and match.group(1)[-1] == "s":
+                        print(
+                            "'Authors' should to be changed to 'Author' in {0}".format(
+                                currentFilepath
+                            )
+                        )
+                    if len(fileAuthors) > 1 and match.group(1)[-1] != "s":
+                        print(
+                            "'Author' should to be changed to 'Authors' in {0}".format(
+                                currentFilepath
+                            )
+                        )
 
                     # Save statistics on authors
                     for author in fileAuthors:
@@ -153,29 +182,35 @@ def check_copyright(directory):
                     averageAuthorsPerFile += len(fileAuthors)
     averageAuthorsPerFile /= totalCheckdFilesCounter
 
-    print('Checked {} files'.format(totalCheckdFilesCounter))
+    print("Checked {} files".format(totalCheckdFilesCounter))
     if filesWithErrorsCounter == 0:
-        print('All the files have the correct copyright notice')
+        print("All the files have the correct copyright notice")
     else:
-        print(Colors.RED + '{:.1f}% ({}/{}) of all analyzed files do not match with the copyright template!'.format(
-            100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter) + Colors.RESET)
-
-    if (not args.quiet):
-        print('{:.2} authors per file'.format(
-            averageAuthorsPerFile))
-
-        print('Number of mentions per author:')
+        print(
+            Colors.RED
+            + "{:.1f}% ({}/{}) of all analyzed files do not match with the copyright template!".format(
+                100 * filesWithErrorsCounter / totalCheckdFilesCounter,
+                filesWithErrorsCounter,
+                totalCheckdFilesCounter,
+            )
+            + Colors.RESET
+        )
+
+    if not args.quiet:
+        print("{:.2} authors per file".format(averageAuthorsPerFile))
+
+        print("Number of mentions per author:")
         for author in sorted(authors.items(), key=lambda item: item[1], reverse=True):
-            print('{:3} - {}'.format(author[1], author[0]))
+            print("{:3} - {}".format(author[1], author[0]))
 
     # Exit if error if at least one file isn't correct
-    if (filesWithErrorsCounter > 0):
+    if filesWithErrorsCounter > 0:
         exit(-1)
 
 
 # Checks if all .h and .cpp files respects the clang format specified in .clang-format file
 def check_format(directory):
-    print(Colors.GREEN + 'Formatting check' + Colors.RESET)
+    print(Colors.GREEN + "Formatting check" + Colors.RESET)
 
     # Statistics
     totalCheckdFilesCounter = 0
@@ -183,7 +218,7 @@ def check_format(directory):
 
     # Walk throgh the directory and check each file
     for dirpath, dirnames, filenames in walk(directory):
-        for filename in [f for f in filenames if f.endswith(('.cpp', '.h', 'c'))]:
+        for filename in [f for f in filenames if f.endswith((".cpp", ".h", "c"))]:
             totalCheckdFilesCounter += 1
 
             # Prepare the complete filepath
@@ -191,31 +226,57 @@ def check_format(directory):
 
             # Dry run clang-format and check if we have an error
             returnCode = call(
-                ['clang-format', '-style=file', '--dry-run', '--Werror', '--ferror-limit=1', currentFilepath], stderr=DEVNULL)
+                [
+                    "clang-format",
+                    "-style=file",
+                    "--dry-run",
+                    "--Werror",
+                    "--ferror-limit=1",
+                    currentFilepath,
+                ],
+                stderr=DEVNULL,
+            )
 
             # If and error occurs warn the user
-            if (returnCode != 0):
+            if returnCode != 0:
                 filesWithErrorsCounter += 1
 
-                if (not args.quiet):
-                    print(Colors.YELLOW + 'Wrong code format for file {1}'.format(
-                        returnCode, currentFilepath) + Colors.RESET)
+                if not args.quiet:
+                    print(
+                        Colors.YELLOW
+                        + "Wrong code format for file {1}".format(
+                            returnCode, currentFilepath
+                        )
+                        + Colors.RESET
+                    )
 
-    print('Checked {} files'.format(totalCheckdFilesCounter))
+    print("Checked {} files".format(totalCheckdFilesCounter))
     if filesWithErrorsCounter == 0:
-        print('All the files match the Skyward formatting style')
+        print("All the files match the Skyward formatting style")
     else:
-        print(Colors.RED + '{:4.1f}% ({}/{}) of all analyzed files do not match Skyward formatting style!'.format(
-            100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter), Colors.RESET)
+        print(
+            Colors.RED
+            + "{:4.1f}% ({}/{}) of all analyzed files do not match Skyward formatting style!".format(
+                100 * filesWithErrorsCounter / totalCheckdFilesCounter,
+                filesWithErrorsCounter,
+                totalCheckdFilesCounter,
+            ),
+            Colors.RESET,
+        )
 
     # Exit if error if at least one file isn't correct
-    if (filesWithErrorsCounter > 0):
+    if filesWithErrorsCounter > 0:
         exit(-1)
 
 
-def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFilter=None):
+def find_in_code(
+    directory, searchTerm, extensionFilters=(".cpp", ".h"), pathFilter=None
+):
     print(
-        Colors.GREEN + 'Checking for \'{}\' in code files'.format(searchTerm) + Colors.RESET)
+        Colors.GREEN
+        + "Checking for '{}' in code files".format(searchTerm)
+        + Colors.RESET
+    )
 
     # Statistics
     totalCheckdFilesCounter = 0
@@ -223,7 +284,12 @@ def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFil
 
     # Walk through the directory and check each file
     for dirpath, dirnames, filenames in walk(directory):
-        for filename in [f for f in filenames if f.endswith(extensionFilters) and (not pathFilter or dirpath.find(pathFilter) >= 0)]:
+        for filename in [
+            f
+            for f in filenames
+            if f.endswith(extensionFilters)
+            and (not pathFilter or dirpath.find(pathFilter) >= 0)
+        ]:
             totalCheckdFilesCounter += 1
 
             # Prepare the complete filepath
@@ -233,67 +299,95 @@ def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFil
             with open(currentFilepath) as file:
                 fileContent = file.read()
                 # Check for linter off flag
-                if re.search('linter off', fileContent, re.M):
+                if re.search("linter off", fileContent, re.M):
                     continue
                 match = re.search(searchTerm, fileContent, re.M)
                 if match:
                     filesWithErrorsCounter += 1
 
                     # The current file has the error
-                    if (not args.quiet):
-                        print(Colors.YELLOW + 'Found \'{}\' in file {}'.format(searchTerm,
-                                                                               currentFilepath) + Colors.RESET)
-
-    print('Checked {} files'.format(totalCheckdFilesCounter))
+                    if not args.quiet:
+                        print(
+                            Colors.YELLOW
+                            + "Found '{}' in file {}".format(
+                                searchTerm, currentFilepath
+                            )
+                            + Colors.RESET
+                        )
+
+    print("Checked {} files".format(totalCheckdFilesCounter))
     if filesWithErrorsCounter == 0:
-        print(
-            'All the files does not contain \'{}\''.format(searchTerm))
+        print("All the files do not contain '{}'".format(searchTerm))
     else:
-        print(Colors.RED + '{:.1f}% ({}/{}) of all analyzed files contain \'{}\'!'.format(
-            100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter, searchTerm) + Colors.RESET)
+        print(
+            Colors.RED
+            + "{:.1f}% ({}/{}) of all analyzed files contain '{}'!".format(
+                100 * filesWithErrorsCounter / totalCheckdFilesCounter,
+                filesWithErrorsCounter,
+                totalCheckdFilesCounter,
+                searchTerm,
+            )
+            + Colors.RESET
+        )
 
     return filesWithErrorsCounter
 
 
 def check_find(directory):
-    sum = find_in_code(directory, r'^using namespace', '.h')
-    sum += find_in_code(directory,
-                        r'[^a-zA-Z0-9]printf\(', pathFilter='shared')
-    sum += find_in_code(directory, '^ *throw ', pathFilter='catch')
+    sum = find_in_code(directory, r"^using namespace", ".h")
+    sum += find_in_code(directory, r"[^a-zA-Z0-9]printf\(", pathFilter="shared")
+    sum += find_in_code(directory, "^ *throw ", pathFilter="catch")
 
     if sum > 0:
         exit(-1)
 
 
 def check_cppcheck(directory):
-    print(Colors.GREEN + 'cppcheck' + Colors.RESET)
+    print(Colors.GREEN + "cppcheck" + Colors.RESET)
     # Run cppcheck on the directory
     try:
-        result = check_output(['cppcheck', '-q', '--language=c++', '--template=gcc', '--std=c++11', '--enable=all', '--inline-suppr',
-                               '--suppress=unmatchedSuppression', '--suppress=unusedFunction', '--suppress=missingInclude',
-                               directory], stderr=STDOUT)
+        result = check_output(
+            [
+                "cppcheck",
+                "-q",
+                "--language=c++",
+                "--template=gcc",
+                "--std=c++11",
+                "--enable=all",
+                "--inline-suppr",
+                "--suppress=unmatchedSuppression",
+                "--suppress=unusedFunction",
+                "--suppress=missingInclude",
+                directory,
+            ],
+            stderr=STDOUT,
+        )
 
         # Parse results and count errors
-        errors = re.findall(r'\[(\w+)\]', result.decode('utf-8'))
+        errors = re.findall(r"\[(\w+)\]", result.decode("utf-8"))
         errors = Counter(errors)
 
-        if (not args.quiet):
-            print('cppcheck found the following errors:')
+        if not args.quiet:
+            print("cppcheck found the following errors:")
             for error in errors:
-                print('{:3} - {}'.format(errors[error], error))
+                print("{:3} - {}".format(errors[error], error))
 
         totalErrors = sum(errors.values())
-        if (totalErrors > 0):
+        if totalErrors > 0:
             print(
-                Colors.RED + 'cppcheck found {} errors in total'.format(totalErrors) + Colors.RESET)
+                Colors.RED
+                + "cppcheck found {} errors in total".format(totalErrors)
+                + Colors.RESET
+            )
             exit(-1)
         else:
-            print('cppcheck did not find any errors')
+            print("cppcheck did not find any errors")
 
     except CalledProcessError as e:
-        print(e.output.decode('utf-8'))
+        print(e.output.decode("utf-8"))
         exit(-1)
 
+
 # -------------------------------------------------------------
 # MAIN
 # -------------------------------------------------------------
@@ -302,26 +396,26 @@ def check_cppcheck(directory):
 parser = config_cmd_parser()
 args = parser.parse_args()
 
-if (not args.directory):
-    print('No directory specified')
-    print('')
+if not args.directory:
+    print("No directory specified")
+    print("")
     parser.print_help()
     exit(-1)
 
-if (args.copyright):
+if args.copyright:
     check_copyright(args.directory)
 
-if (args.format):
+if args.format:
     check_format(args.directory)
 
-if (args.cppcheck):
+if args.cppcheck:
     check_cppcheck(args.directory)
 
-if (args.find):
+if args.find:
     check_find(args.directory)
 
 # Checks everything if no option is specified
-if (not args.copyright and not args.format and not args.find and not args.cppcheck):
+if not args.copyright and not args.format and not args.find and not args.cppcheck:
     check_copyright(args.directory)
     check_format(args.directory)
     check_find(args.directory)
diff --git a/src/shared/sensors/Vectornav/VN300/VN300.cpp b/src/shared/sensors/Vectornav/VN300/VN300.cpp
index 55feeebda4484a9e9dc8d6e77f541bfd8fb64a5b..ef7b62bfab699427d3ef7cb2fa8a1e43869ff780 100755
--- a/src/shared/sensors/Vectornav/VN300/VN300.cpp
+++ b/src/shared/sensors/Vectornav/VN300/VN300.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/shared/sensors/Vectornav/VN300/VN300.h b/src/shared/sensors/Vectornav/VN300/VN300.h
index 9bf642020b9c6990dd3db4ec3303c9e3c0348846..a106bdf98d13e2476289a1874dcc3af20fcbc1bc 100755
--- a/src/shared/sensors/Vectornav/VN300/VN300.h
+++ b/src/shared/sensors/Vectornav/VN300/VN300.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/shared/sensors/Vectornav/VN300/VN300Data.h b/src/shared/sensors/Vectornav/VN300/VN300Data.h
index 538fecbfc5310079dee9c7ab4e7e83b002777b36..5953da14c339b1f849706fa70a792cf338e6625b 100644
--- a/src/shared/sensors/Vectornav/VN300/VN300Data.h
+++ b/src/shared/sensors/Vectornav/VN300/VN300Data.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/shared/sensors/Vectornav/VN300/VN300Defs.h b/src/shared/sensors/Vectornav/VN300/VN300Defs.h
index b7037a26b36a03aabd3c6b51bd527b10b62b0cff..7e2b186d0ce0263e18806a2fe65acafce8336796 100755
--- a/src/shared/sensors/Vectornav/VN300/VN300Defs.h
+++ b/src/shared/sensors/Vectornav/VN300/VN300Defs.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.cpp b/src/shared/sensors/Vectornav/VNCommonSerial.cpp
index c4859c0b6cead5e27d26d70ce0b0b18d190fffc1..599e889054fe4b7ea70ecc0cb96e04b1f9e6a6a5 100644
--- a/src/shared/sensors/Vectornav/VNCommonSerial.cpp
+++ b/src/shared/sensors/Vectornav/VNCommonSerial.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2024 Skyward Experimental Rocketry
- * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.h b/src/shared/sensors/Vectornav/VNCommonSerial.h
index 08c64c3d50238b225928915d5034901942d44e7d..e25b6d2256597ff7b77be1674b8931cd24e869d2 100644
--- a/src/shared/sensors/Vectornav/VNCommonSerial.h
+++ b/src/shared/sensors/Vectornav/VNCommonSerial.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2024 Skyward Experimental Rocketry
- * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
diff --git a/src/tests/sensors/test-vn300.cpp b/src/tests/sensors/test-vn300.cpp
index a9d5cbaaffb9ffd257d6cf0f425cded50708e891..408165eb3ec1d021767c4fe705242a927c83a368 100644
--- a/src/tests/sensors/test-vn300.cpp
+++ b/src/tests/sensors/test-vn300.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Lorenzo Cucchi, Fabrizio Monti
+ * Authors: Lorenzo Cucchi, Fabrizio Monti
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal