const std = @import("std"); const input = @import("input.zig"); const protocol = @import("protocol.zig"); const replay = @import("replay.zig"); const session = @import("session.zig"); const socket = @import("socket.zig"); const tui = @import("tui.zig"); pub const version = "0.1.0-dev"; const help_text = \\mim - mobile-first terminal code editor for SSH sessions \\ \\Usage: \\ mim [--help] \\ mim [--version] \\ mim --mimctl \\ \\This early build exposes the canonical smoke path and a minimal local \\session-control client for trusted tools. \\ ; const Command = union(enum) { help, version, smoke, unknown: []const u8, }; fn parseArgs(args: []const []const u8) Command { if (args.len <= 1) return .smoke; const arg = args[1]; if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) return .help; if (std.mem.eql(u8, arg, "--version")) return .version; return .{ .unknown = arg }; } fn versionText() []const u8 { return "mim " ++ version ++ "\n"; } pub fn main(init: std.process.Init) !u8 { var debug_allocator = std.heap.DebugAllocator(.{}){}; defer _ = debug_allocator.deinit(); const allocator = debug_allocator.allocator(); var args = std.process.Args.Iterator.init(init.minimal.args); _ = args.skip(); const stdout = std.Io.File.stdout(); const stderr = std.Io.File.stderr(); const first_arg = args.next(); if (first_arg) |arg| { if (std.mem.eql(u8, arg, "--mimctl")) { const socket_path = args.next() orelse { try stderr.writeStreamingAll(init.io, "mim: --mimctl requires a socket path\n"); return 64; }; const line = try collectRemainingArgs(allocator, &args); defer allocator.free(line); if (line.len == 0) { try stderr.writeStreamingAll(init.io, "mim: --mimctl requires a request\n"); return 64; } const resolved_socket_path = if (std.mem.eql(u8, socket_path, "-")) std.process.Environ.getAlloc(init.minimal.environ, allocator, socket.socket_env_name) catch { try stderr.writeStreamingAll(init.io, "mim: MIM_SOCKET is not set\n"); return 69; } else try allocator.dupe(u8, socket_path); defer allocator.free(resolved_socket_path); const response = socket.request(allocator, resolved_socket_path, line) catch { try stderr.writeStreamingAll(init.io, "mim: mimctl request failed\n"); return 69; }; defer allocator.free(response); try stdout.writeStreamingAll(init.io, response); return 0; } } const command = if (first_arg) |arg| parseArgs(&.{ "mim", arg }) else parseArgs(&.{"mim"}); switch (command) { .help => try stdout.writeStreamingAll(init.io, help_text), .version => try stdout.writeStreamingAll(init.io, versionText()), .smoke => try stdout.writeStreamingAll(init.io, help_text), .unknown => |arg| { try stderr.writeStreamingAll(init.io, "mim: unknown argument '"); try stderr.writeStreamingAll(init.io, arg); try stderr.writeStreamingAll(init.io, "'\nTry 'mim --help'.\n"); return 64; }, } return 0; } fn collectRemainingArgs(allocator: std.mem.Allocator, args: *std.process.Args.Iterator) ![]u8 { var line = std.ArrayList(u8).empty; errdefer line.deinit(allocator); var first = true; while (args.next()) |arg| { if (!first) try line.append(allocator, ' '); try line.appendSlice(allocator, arg); first = false; } return line.toOwnedSlice(allocator); } test { _ = input; _ = protocol; _ = replay; _ = session; _ = socket; _ = tui; } test "regular: help text names the binary and smoke boundary" { try std.testing.expect(std.mem.indexOf(u8, help_text, "mim - mobile-first terminal code editor") != null); try std.testing.expect(std.mem.indexOf(u8, help_text, "canonical smoke path") != null); } test "regular: version output is stable enough for smoke checks" { try std.testing.expectEqualStrings("mim 0.1.0-dev\n", versionText()); } test "adversarial: unknown arguments are rejected instead of ignored" { const argv = [_][]const u8{ "mim", "--definitely-not-supported" }; const parsed = parseArgs(&argv); switch (parsed) { .unknown => |arg| try std.testing.expectEqualStrings("--definitely-not-supported", arg), else => return error.ExpectedUnknownArgument, } } test "adversarial: extra positional arguments are not treated as files yet" { const argv = [_][]const u8{ "mim", "some-file.zig" }; const parsed = parseArgs(&argv); switch (parsed) { .unknown => |arg| try std.testing.expectEqualStrings("some-file.zig", arg), else => return error.ExpectedUnknownArgument, } }