commit 86bce941de2c41101ebed43e01865968ba89d1b4
Author: Nathan Anderson <nathananderson98@gmail.com>
Date:   Mon Oct 9 22:54:00 2023 -0600

    First commit, things mostly work locally, on linux x86

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4a0641e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+zig-cache/
+zig-out/
\ No newline at end of file
diff --git a/.ignore b/.ignore
new file mode 100644
index 0000000..e73c965
--- /dev/null
+++ b/.ignore
@@ -0,0 +1,2 @@
+zig-cache/
+zig-out/
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..2210c88
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,70 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{});
+
+    // Standard optimization options allow the person running `zig build` to select
+    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+    // set a preferred release mode, allowing the user to decide how to optimize.
+    const optimize = b.standardOptimizeOption(.{});
+
+    const exe = b.addExecutable(.{
+        .name = "zim",
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // This declares intent for the executable to be installed into the
+    // standard location when the user invokes the "install" step (the default
+    // step when running `zig build`).
+    b.installArtifact(exe);
+
+    // This *creates* a Run step in the build graph, to be executed when another
+    // step is evaluated that depends on it. The next line below will establish
+    // such a dependency.
+    const run_cmd = b.addRunArtifact(exe);
+
+    // By making the run step depend on the install step, it will be run from the
+    // installation directory rather than directly from within the cache directory.
+    // This is not necessary, however, if the application depends on other installed
+    // files, this ensures they will be present and in the expected location.
+    run_cmd.step.dependOn(b.getInstallStep());
+
+    // This allows the user to pass arguments to the application in the build
+    // command itself, like this: `zig build run -- arg1 arg2 etc`
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build run`
+    // This will evaluate the `run` step rather than the default, which is "install".
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+
+    // Creates a step for unit testing. This only builds the test executable
+    // but does not run it.
+    const unit_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const run_unit_tests = b.addRunArtifact(unit_tests);
+
+    // Similar to creating the run step earlier, this exposes a `test` step to
+    // the `zig build --help` menu, providing a way for the user to request
+    // running the unit tests.
+    const test_step = b.step("test", "Run unit tests");
+    test_step.dependOn(&run_unit_tests.step);
+}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..8639573
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,237 @@
+const std = @import("std");
+const zim = @import("zim.zig");
+const utils = @import("utils.zig");
+
+const string = []const u8;
+
+pub const ZIM_MAJOR_VER = 0;
+pub const ZIM_MINOR_VER = 0;
+pub const ZIM_BUILD = 1;
+
+/// The availble arguments for the ZiM program
+const Args = enum(u8) {
+    help,
+    install,
+    list,
+    use,
+    gir,
+    version,
+};
+
+/// A type of argument for the ZiM program
+const ArgType = enum {
+    single,
+    expectsOneParam,
+};
+
+/// Allocates a string with the version of Zim, call `allocator.free()` with
+/// the string when done using it.
+pub fn versionStr(allocator: std.mem.Allocator) ![]const u8 {
+    var buf = try allocator.alloc(u8, 10);
+    _ = try std.fmt.bufPrint(buf, "{d}.{d}.{d}", .{ ZIM_MAJOR_VER, ZIM_MINOR_VER, ZIM_BUILD });
+    return buf;
+}
+
+/// Returns what each argument expects to follow it
+fn get_arg_parse_type(arg: Args) ArgType {
+    return switch (arg) {
+        .help => ArgType.single,
+        .install => ArgType.expectsOneParam,
+        .list => ArgType.expectsOneParam,
+        .use => ArgType.expectsOneParam,
+        .gir => ArgType.single,
+        .version => ArgType.single,
+    };
+}
+
+/// For a given `Args`, returns a string description of what each does
+fn command_desc(arg: Args) string {
+    return switch (arg) {
+        .help => "  help\t\tDisplays this help message",
+        .install => "  install\tInstall the zig version",
+        .list => "  list\t\tLists all versions available on the system",
+        .use => "  use\t\tSets the active version of zig to use",
+        .version => "  version\t\tPrint out the zim version you are using",
+        .gir => "  gir\t\tPrint out ascii Gir",
+    };
+}
+
+/// Shows the main help menu for the program
+fn show_help() void {
+    std.debug.print("Welcome to the ZIg version Manager (ZIM)\nHelp Menu:\n\n", .{});
+    inline for (@typeInfo(Args).Enum.fields) |arg_type| {
+        const tag = utils.nameToEnumTag(arg_type.name, Args) catch {
+            unreachable;
+        };
+        std.debug.print("{s}\n", .{command_desc(tag)});
+    }
+    std.debug.print("\n", .{});
+}
+
+/// Shows a help menu for a specific `Args` action, like a help sub-menu
+fn show_context_help(arg: Args) void {
+    switch (arg) {
+        .install => {
+            std.debug.print(
+                \\zim install <version>
+                \\  Used to install zig versions, that will then be available to
+                \\  the `zim use <version>` command.
+                \\
+                \\  To see all available versions to install, run `zim list global`
+                \\
+                \\
+            , .{});
+        },
+        .list => {
+            std.debug.print(
+                \\zim list <local or global>
+                \\  Used to list zig versions, either locally or remotely available.
+                \\  Running zim global will make a network request to fetch the latest
+                \\  versions of zig.
+                \\
+                \\
+            , .{});
+        },
+        .use => {
+            std.debug.print(
+                \\zim use <version>
+                \\  Used to switch the actively used zig version on the system. If
+                \\  your system PATH environment is set to use `~/.config/zim/zig`.
+                \\  If your system is not setup, you can run `zim init` and cross 
+                \\  your fingers that it supports your setup.
+                \\
+                \\
+            , .{});
+        },
+        .gir, .version, .help => {
+            return;
+        },
+    }
+}
+
+/// Given `arg`, does the action with the provided optional `param`
+fn do_arg_action(allocator: std.mem.Allocator, arg: Args, param: ?[]const u8) void {
+    switch (arg) {
+        .help => {
+            show_help();
+        },
+        .gir => {
+            utils.printGir();
+        },
+        .version => {
+            const ver = versionStr(allocator) catch {
+                std.debug.print("Error getting version\n", .{});
+                return;
+            };
+            defer allocator.free(ver);
+
+            if (param) |command| {
+                std.debug.print("{s}\nRunning ZiM Version {s}\n", .{ command, ver });
+            } else {
+                std.debug.print("Running ZiM Version {s}\n", .{ver});
+            }
+
+            utils.printGir();
+        },
+        .install => {
+            if (param == null) {
+                std.debug.print("Error: Expected version to follow\n\n", .{});
+                std.os.exit(1);
+                return;
+            }
+            const p = param.?;
+            if (std.mem.eql(u8, p, "help")) {
+                show_context_help(arg);
+                std.os.exit(0);
+                return;
+            }
+            zim.install(allocator, param.?) catch |err| {
+                std.debug.print("Error running `install {s}`\n{any}", .{ param.?, err });
+                std.os.exit(1);
+            };
+        },
+        .list => {
+            if (param == null) {
+                std.debug.print("Error: Expected `local` or `global` to follow.\nRun `zim list help` for details.\n\n", .{});
+                std.os.exit(1);
+            }
+            const p = param.?;
+            if (std.mem.eql(u8, p, "help")) {
+                show_context_help(arg);
+                std.os.exit(0);
+                return;
+            }
+            if (!std.mem.eql(u8, p, "local") and !std.mem.eql(u8, p, "global")) {
+                std.debug.print("Error: List available versions either `list local` or `list global`\n\n", .{});
+                show_context_help(arg);
+                std.os.exit(1);
+            }
+            zim.list(allocator, param.?) catch |err| {
+                std.debug.print("Error running `list {s}`\n{any}", .{ param.?, err });
+                std.os.exit(1);
+            };
+        },
+        .use => {
+            if (param == null) {
+                std.debug.print("Error: Expected version to follow\n\n", .{});
+                std.os.exit(1);
+            }
+            const p = param.?;
+            if (std.mem.eql(u8, p, "help")) {
+                show_context_help(arg);
+                std.os.exit(0);
+                return;
+            }
+            zim.use(allocator, param.?) catch |err| {
+                std.debug.print("Error running `use {s}`\n{any}", .{ param.?, err });
+                std.os.exit(1);
+            };
+        },
+    }
+}
+
+pub fn main() !void {
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+    const allocator = gpa.allocator();
+
+    var args_iter = try std.process.argsWithAllocator(allocator);
+    defer args_iter.deinit();
+
+    // var parsed_args = std.ArrayList(string).init(allocator);
+    // defer parsed_args.deinit();
+
+    const valid = arg_loop: {
+        var index: u8 = 0;
+        const command = args_iter.next();
+
+        while (args_iter.next()) |arg| {
+            index += 1;
+            const arg_tag = utils.nameToEnumTag(arg, Args) catch {
+                break :arg_loop false;
+            };
+            var param: ?[]const u8 = null;
+            if (get_arg_parse_type(arg_tag) == ArgType.expectsOneParam) {
+                param = args_iter.next();
+            }
+            if (arg_tag == .version) {
+                do_arg_action(
+                    allocator,
+                    arg_tag,
+                    command,
+                );
+                break;
+            }
+            do_arg_action(allocator, arg_tag, param);
+            break;
+        }
+        if (index == 0) {
+            show_help();
+        }
+        break :arg_loop true;
+    };
+
+    if (!valid) {
+        std.debug.print("Something is not right with the command, try running `zim help` for details.\n", .{});
+        std.os.exit(1);
+    }
+}
diff --git a/src/utils.zig b/src/utils.zig
new file mode 100644
index 0000000..899c352
--- /dev/null
+++ b/src/utils.zig
@@ -0,0 +1,47 @@
+const std = @import("std");
+
+const enumError = error{EnumFieldNotFound};
+
+pub fn nameToEnumTag(name: []const u8, comptime Enum: type) enumError!Enum {
+    comptime {
+        if (@typeInfo(Enum) != .Enum) {
+            @compileError("Non enum type passed into function: " ++ @typeName(Enum));
+        }
+    }
+
+    inline for (@typeInfo(Enum).Enum.fields) |field| {
+        if (std.mem.eql(u8, name, field.name)) {
+            const tag: Enum = @enumFromInt(field.value);
+            return tag;
+        }
+    }
+    return enumError.EnumFieldNotFound;
+}
+
+test {
+    const State = enum {
+        good,
+        bad,
+    };
+
+    const enum_tag = try nameToEnumTag("good", State);
+    try std.testing.expect(enum_tag == State.good);
+}
+
+pub fn printGir() void {
+    std.debug.print(
+        \\                   n    n 
+        \\         o       (        )
+        \\     ___/_      /   I am   \
+        \\    /    / . o (    your    )
+        \\   0>  0 /      \   Doom!  /
+        \\    Lu__/        (        )
+        \\     ||            - u  -
+        \\   o^  ^ o
+        \\ _/ /  /  \_
+        \\    |__|
+        \\ O=>  (O
+        \\
+        \\
+    , .{});
+}
diff --git a/src/version.zig b/src/version.zig
new file mode 100644
index 0000000..8c653a6
--- /dev/null
+++ b/src/version.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const string = []const u8;
+
+pub const ZigVersion = struct {
+    allocator: std.mem.Allocator,
+    version_string: string,
+    tarball_url: ?string,
+    src_tarball_url: ?string,
+    platform_string: ?string,
+    docs_url: ?string,
+
+    pub fn init(allocator: std.mem.Allocator, scanner: *std.json.Scanner, ver_string: string) ZigVersion {
+        var version_string = allocator.dupe(u8, ver_string) catch unreachable;
+        var docs_url: ?string = null;
+        var tarball_url: ?string = null;
+        var platform_string: ?string = null;
+        var src_tarball_url: ?string = null;
+
+        var scanning = true;
+        while (scanning) {
+            if (scanner.*.next()) |token| {
+                switch (token) {
+                    .object_begin => {},
+                    .object_end => {
+                        const t_type = scanner.*.peekNextTokenType() catch {
+                            unreachable;
+                        };
+                        if (t_type == std.json.TokenType.object_end) {
+                            scanning = false;
+                        }
+                    },
+                    .string => {
+                        if (std.mem.eql(u8, token.string, "docs")) {
+                            const docs_token = scanner.*.next() catch unreachable;
+                            docs_url = allocator.dupe(u8, docs_token.string) catch unreachable;
+                        } else if (std.mem.eql(u8, token.string, "src")) {
+                            // ignore the object begin
+                            checkNextToken(scanner, .object_begin);
+                            _ = scanner.*.next() catch unreachable;
+                            // ignore tarball string
+                            checkNextToken(scanner, .string);
+                            _ = scanner.*.next() catch unreachable;
+                            const tar_token = scanner.*.next() catch unreachable;
+                            src_tarball_url = allocator.dupe(u8, tar_token.string) catch unreachable;
+                        } else if (std.mem.eql(u8, token.string, "x86_64-linux")) {
+                            platform_string = allocator.dupe(u8, token.string) catch unreachable;
+                            // ignore object begin
+                            checkNextToken(scanner, .object_begin);
+                            _ = scanner.*.next() catch unreachable;
+                            checkNextToken(scanner, .string);
+                            const tar_token = scanner.*.next() catch unreachable;
+                            if (std.mem.eql(u8, tar_token.string, "tarball")) {
+                                const tar_url_token = scanner.*.next() catch unreachable;
+                                tarball_url = allocator.dupe(u8, tar_url_token.string) catch unreachable;
+                            }
+                        }
+                    },
+                    else => {
+                        unreachable;
+                    },
+                }
+            } else |err| {
+                std.debug.print("Error scanning in ZigVersion: {any}\n", .{err});
+                scanning = false;
+                unreachable;
+            }
+        }
+
+        return ZigVersion{ .allocator = allocator, .version_string = version_string, .tarball_url = tarball_url, .src_tarball_url = src_tarball_url, .platform_string = platform_string, .docs_url = docs_url };
+    }
+
+    pub fn deinit() void {}
+
+    pub fn fmtPrint(zv: *const ZigVersion) void {
+        std.debug.print("  zig-{s}\n", .{zv.version_string});
+        if (zv.platform_string) |plat| {
+            std.debug.print("\tplatform: {s}\n", .{plat});
+        }
+        if (zv.docs_url) |docs| {
+            std.debug.print("\tdocs_url: {s}\n", .{docs});
+        }
+        if (zv.tarball_url) |tar| {
+            std.debug.print("\ttar_url: {s}\n", .{tar});
+        }
+        if (zv.src_tarball_url) |tar| {
+            std.debug.print("\tsrc_tar_url: {s}\n", .{tar});
+        }
+    }
+
+    pub fn hasTar(zv: *ZigVersion) bool {
+        return zv.tarball_url != null;
+    }
+};
+
+fn checkNextToken(scanner: *std.json.Scanner, t_type: std.json.TokenType) void {
+    const t = scanner.*.peekNextTokenType() catch unreachable;
+    if (t != t_type) {
+        std.debug.print("Expected to find {any} but got {any}\n", .{ t_type, t });
+        unreachable;
+    }
+}
diff --git a/src/zim.zig b/src/zim.zig
new file mode 100644
index 0000000..5d38144
--- /dev/null
+++ b/src/zim.zig
@@ -0,0 +1,348 @@
+const std = @import("std");
+const utils = @import("utils.zig");
+const version = @import("version.zig");
+const string = []const u8;
+
+const ZigVersion = version.ZigVersion;
+
+const ZimError = error{
+    BadParameter,
+    MalformedJson,
+    NetworkError,
+    NotImplemented,
+    NoHomeConfigured,
+    Unexpected,
+};
+
+const BIN_SYM_ZIG_PATH = "bin/zig";
+
+fn createSubDir(dir: std.fs.Dir, path: string) ZimError!void {
+    dir.makeDir(path) catch |err| blk: {
+        if (err == error.PathAlreadyExists) {
+            // std.debug.print("{s} already exists\n", .{path});
+            break :blk;
+        }
+        std.debug.print("Encountered error creating directory {s}: {any}\n", .{ path, err });
+        return ZimError.Unexpected;
+    };
+}
+
+fn openZimDir(allocator: std.mem.Allocator) ZimError!std.fs.Dir {
+    const home_dir = std.os.getenv("HOME");
+    if (home_dir == null) {
+        return ZimError.NoHomeConfigured;
+    }
+    // std.debug.print("Found home env to be: {s}\n", .{home_dir.?});
+    const zimPath = std.mem.concat(allocator, u8, &[_][]const u8{ home_dir.?, "/.config/zim" }) catch {
+        return ZimError.Unexpected;
+    };
+    defer allocator.free(zimPath);
+
+    // std.debug.print("Creating zim directory in {s}\n", .{zimPath});
+    std.fs.makeDirAbsolute(zimPath) catch |err| blk: {
+        if (err == error.PathAlreadyExists) {
+            // std.debug.print("Do not need to create new directory\n", .{});
+            break :blk;
+        }
+        std.debug.print("Error creating directory: {any}\n", .{err});
+        return ZimError.Unexpected;
+    };
+
+    var zim_dir = std.fs.openDirAbsolute(zimPath, .{}) catch {
+        // std.debug.print("Encountered error opening directory: {any}\n", .{err});
+        return ZimError.Unexpected;
+    };
+
+    try createSubDir(zim_dir, "bin");
+    try createSubDir(zim_dir, "versions");
+    return zim_dir;
+}
+
+fn getLocalVersionsList(
+    allocator: std.mem.Allocator,
+    zim_dir: std.fs.Dir,
+) ZimError!std.ArrayList(string) {
+    var versions_dir = zim_dir.openIterableDir("versions", .{}) catch {
+        return ZimError.Unexpected;
+    };
+    defer versions_dir.close();
+
+    var ver_walker = versions_dir.walk(allocator) catch {
+        return ZimError.Unexpected;
+    };
+    defer ver_walker.deinit();
+
+    var versions_list = std.ArrayList(string).init(allocator);
+
+    var walking = true;
+    while (walking) blk: {
+        var ver = ver_walker.next() catch {
+            walking = false;
+            break :blk;
+        };
+        if (ver == null) {
+            walking = false;
+            break :blk;
+        }
+        // Dont recursively enter into any of the zig directories
+        if (!std.mem.eql(u8, ver.?.basename, ver.?.path)) {
+            break :blk;
+        }
+        if (ver.?.kind == .directory) {
+            const path = ver.?.path;
+            const start_name = path[0..3];
+            if (std.mem.eql(u8, start_name, "zig")) {
+                var p = allocator.dupe(u8, path) catch {
+                    return ZimError.Unexpected;
+                };
+
+                versions_list.append(p) catch {
+                    return ZimError.Unexpected;
+                };
+            }
+        }
+        // std.debug.print("Got entry in `versions:` base: {s}, path: {s}, kind: {any}\n", .{ ver.?.basename, ver.?.path, ver.?.kind });
+    }
+    return versions_list;
+}
+
+fn getRemoteVersionsList(allocator: std.mem.Allocator) ZimError!std.ArrayList(ZigVersion) {
+    const versions_json_url = "https://ziglang.org/download/index.json";
+    const versions_json_uri = std.Uri.parse(versions_json_url) catch {
+        return ZimError.Unexpected;
+    };
+
+    var headers = std.http.Headers{ .allocator = allocator };
+    defer headers.deinit();
+
+    // Accept anything.
+    headers.append("accept", "*/*") catch {
+        std.debug.print("Error adding headers\n", .{});
+        return ZimError.Unexpected;
+    };
+    var client = std.http.Client{ .allocator = allocator };
+    defer client.deinit();
+
+    var request = client.request(.GET, versions_json_uri, headers, .{}) catch {
+        std.debug.print("Error creating request\n", .{});
+        return ZimError.NetworkError;
+    };
+    defer request.deinit();
+
+    request.start() catch {
+        std.debug.print("Error starting request\n", .{});
+        return ZimError.NetworkError;
+    };
+    std.debug.print("Querying ziglang.org for latest zig versions...\n", .{});
+    request.wait() catch {
+        std.debug.print("Error waiting for request\n", .{});
+        return ZimError.NetworkError;
+    };
+
+    const sixty_four_kilobytes = 65536;
+    const body = request.reader().readAllAlloc(allocator, sixty_four_kilobytes) catch |err| {
+        std.debug.print("Error getting body: {any}\n", .{err});
+        return ZimError.NetworkError;
+    };
+    defer allocator.free(body);
+
+    var diag = std.json.Diagnostics{};
+    var body_scanner = std.json.Scanner.initCompleteInput(allocator, body);
+    body_scanner.enableDiagnostics(&diag);
+
+    var remote_zig_versions = std.ArrayList(ZigVersion).init(allocator);
+
+    var scanning = true;
+    var last_version: string = "";
+    while (scanning) {
+        if (body_scanner.next()) |token| {
+            switch (token) {
+                .object_begin => {
+                    const zv = ZigVersion.init(allocator, &body_scanner, last_version);
+                    remote_zig_versions.append(zv) catch {
+                        return ZimError.Unexpected;
+                    };
+                },
+                .end_of_document => {
+                    scanning = false;
+                },
+                .string => {
+                    last_version = token.string;
+                },
+                else => {},
+            }
+        } else |err| switch (err) {
+            error.SyntaxError => {
+                std.debug.print("Syntax error at line {d} col {d}\n", .{ diag.line_number, diag.getColumn() });
+                scanning = false;
+            },
+            else => {
+                std.debug.print("Got error {any}\n", .{err});
+                scanning = false;
+            },
+        }
+    }
+    return remote_zig_versions;
+}
+
+const ShellType = enum {
+    bash,
+    zsh,
+};
+
+fn shellName(s: ?ShellType) string {
+    comptime switch (s) {
+        .bash => "bash",
+        .zsh => "zsh",
+    };
+}
+
+fn printZimPathHelp(shell_tag: ?ShellType, zim_path: string) void {
+    if (shell_tag == undefined) {
+        std.debug.print("Unrecognized shell\n", .{});
+        return;
+    }
+    const t = "something";
+    _ = t;
+    switch (shell_tag.?) {
+        .bash => {
+            std.debug.print("\nDetected shell as {s}\n", .{@tagName(shell_tag.?)});
+            std.debug.print("For ZiM to work, you need to add\n'{s}'\nto your PATH. To do that, you can\nrun the following command:\n\n", .{zim_path});
+            std.debug.print("\techo \"export PATH={s}:$PATH\" >> ~/.zshrc\n\nOr edit your .zshrc to add it manually.", .{zim_path});
+        },
+        .zsh => {
+            std.debug.print("\nDetected shell as {s}\n", .{@tagName(shell_tag.?)});
+            std.debug.print("For ZiM to work, you need to add\n'{s}'\nto your PATH. To do that, you can\nrun the following command:\n\n", .{zim_path});
+            std.debug.print("\techo \"export PATH={s}:$PATH\" >> ~/.zshrc\n\nOr edit your .zshrc to add it manually.", .{zim_path});
+        },
+    }
+}
+
+pub fn install(allocator: std.mem.Allocator, param: string) ZimError!void {
+    _ = param;
+
+    var zim_dir = try openZimDir(allocator);
+    defer zim_dir.close();
+
+    return ZimError.NotImplemented;
+}
+
+pub fn use(allocator: std.mem.Allocator, param: string) ZimError!void {
+    var zim_dir = try openZimDir(allocator);
+    defer zim_dir.close();
+
+    var versions_list = try getLocalVersionsList(allocator, zim_dir);
+    defer versions_list.deinit();
+
+    const version_num = std.fmt.parseInt(u8, param, 0) catch null;
+    if (version_num != null) {
+        const num = version_num.?;
+        if (num > versions_list.items.len) {
+            std.debug.print("{d} exceeds the range of available versions: {d}\n", .{ num, versions_list.items.len });
+            return ZimError.BadParameter;
+        }
+        const ver_path = versions_list.items[version_num.? - 1];
+        std.debug.print("Using version {s}\n", .{ver_path});
+
+        const zim_path = zim_dir.realpathAlloc(allocator, "./") catch "err";
+        defer allocator.free(zim_path);
+
+        const target_version_path = std.mem.concat(allocator, u8, &[_][]const u8{ zim_path, "/versions/", ver_path }) catch {
+            return ZimError.Unexpected;
+        };
+        defer allocator.free(target_version_path);
+
+        zim_dir.deleteFile(BIN_SYM_ZIG_PATH) catch |err| {
+            if (err != error.FileNotFound) {
+                std.debug.print("Error deleting old link: {any}\n", .{err});
+                return ZimError.Unexpected;
+            }
+        };
+        std.debug.print("Creating sym link to {s}\n", .{target_version_path});
+        zim_dir.symLink(target_version_path, BIN_SYM_ZIG_PATH, .{ .is_directory = true }) catch |err| {
+            std.debug.print("Could not link new version: {any}\n", .{err});
+            return ZimError.Unexpected;
+        };
+
+        const path_environment = std.os.getenv("PATH");
+        if (path_environment) |path_env| {
+            if (!std.mem.containsAtLeast(u8, path_env, 1, zim_path)) {
+                var shell_tag: ?ShellType = null;
+                const shell_env = std.os.getenv("SHELL");
+                if (shell_env) |s| {
+                    var split = std.mem.splitSequence(u8, s, "/");
+                    var shell: []const u8 = "";
+                    while (split.next()) |field| {
+                        shell = field;
+                    }
+                    shell_tag = utils.nameToEnumTag(shell, ShellType) catch null;
+                }
+                const shell_bin_path =
+                    std.mem.concat(allocator, u8, &[_][]const u8{ zim_path, "/", BIN_SYM_ZIG_PATH }) catch "<ZimERR>";
+                printZimPathHelp(
+                    shell_tag,
+                    shell_bin_path,
+                );
+            }
+        }
+        return;
+    } else {
+        std.debug.print("Using raw string version {s}\n", .{param});
+        return ZimError.NotImplemented;
+    }
+
+    // std.debug.print("Local Zig Versions:\n", .{});
+    // var i: u8 = 0;
+    // while (versions_list.popOrNull()) |version| {
+    //     std.debug.print("\n  [{d}]\tZig Version {s}", .{ i + 1, version });
+    //     i += 1;
+    // }
+
+}
+
+const ZimListType = enum { local, global };
+
+/// Runs the `list` subcommand
+pub fn list(allocator: std.mem.Allocator, param: string) !void {
+    const list_type = utils.nameToEnumTag(param, ZimListType) catch {
+        std.debug.print("Unexpected parameter to `list`: {s}\n", .{param});
+        return ZimError.BadParameter;
+    };
+    switch (list_type) {
+        .local => {
+            var zim_dir = try openZimDir(allocator);
+            defer zim_dir.close();
+
+            var versions_list = try getLocalVersionsList(allocator, zim_dir);
+            defer versions_list.deinit();
+
+            std.debug.print("Local Zig Versions:\n", .{});
+            var i: u8 = 0;
+            while (versions_list.popOrNull()) |version_str| {
+                std.debug.print("\n  [{d}]\tZig Version {s}", .{ i + 1, version_str });
+                i += 1;
+            }
+            std.debug.print("\n", .{});
+            std.debug.print(
+                \\
+                \\  Run `zim use` to select which version you want active in your environment.
+                \\  You can also just specify the index of the version you would like to use.
+                \\
+            , .{});
+
+            return;
+        },
+        .global => {
+            var remote_versions = try getRemoteVersionsList(allocator);
+            if (remote_versions.items.len == 0) {
+                std.debug.print("Failed to get remote versions\n", .{});
+                return ZimError.Unexpected;
+            }
+            std.debug.print("Retrieved remote zig versions:\n", .{});
+            for (remote_versions.items) |remote_version| {
+                remote_version.fmtPrint();
+            }
+            return;
+        },
+    }
+}