summaryrefslogtreecommitdiff
path: root/src/Agent.App/Cli/CommandLine.cs
blob: 3b3fde1fb4d80f1da3e1eb798f762ba8ff906244 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
namespace Agent.App.Cli;

public enum AppCommand
{
    Serve,
    Check,
    ExecutableResolve,
    ExecutableApprove,
    ExecutableList,
    ExecutableRevoke,
    SandboxResolve,
    SandboxConfirm,
    SandboxShow
}

public sealed record CommandLine(
    AppCommand Command,
    string? ConfigPath,
    string? Language,
    string? Transport,
    string? Alias = null,
    string? ExecutablePath = null,
    string? RuntimeRoot = null,
    bool NoRuntime = false,
    bool Yes = false,
    string Format = "text")
{
    public static CommandLine Parse(string[] args)
    {
        bool implicitServe = args.Length == 0 || args[0].StartsWith("--", StringComparison.Ordinal);
        int index = implicitServe ? 0 : 1;
        AppCommand command;
        string? alias = null;
        if (implicitServe) command = AppCommand.Serve;
        else if (args[0] == "serve") command = AppCommand.Serve;
        else if (args[0] == "check") command = AppCommand.Check;
        else if (args[0] is "executable" or "sandbox")
        {
            if (index >= args.Length) throw new CommandLineException($"Expected a subcommand after '{args[0]}'.");
            string subcommand = args[index++];
            command = (args[0], subcommand) switch
            {
                ("executable", "resolve") => AppCommand.ExecutableResolve,
                ("executable", "approve") => AppCommand.ExecutableApprove,
                ("executable", "list") => AppCommand.ExecutableList,
                ("executable", "revoke") => AppCommand.ExecutableRevoke,
                ("sandbox", "resolve") => AppCommand.SandboxResolve,
                ("sandbox", "confirm") => AppCommand.SandboxConfirm,
                ("sandbox", "show") => AppCommand.SandboxShow,
                _ => throw new CommandLineException($"Unknown subcommand '{args[0]} {subcommand}'.")
            };
            if (command is AppCommand.ExecutableResolve or AppCommand.ExecutableApprove or AppCommand.ExecutableRevoke)
            {
                if (index >= args.Length || args[index].StartsWith("--", StringComparison.Ordinal))
                    throw new CommandLineException($"Executable Alias is required for '{args[0]} {subcommand}'.");
                alias = args[index++];
            }
        }
        else throw new CommandLineException("Expected 'serve', 'check', 'executable', or 'sandbox'.");

        string? config = null;
        string? language = null;
        string? transport = null;
        string? executablePath = null;
        string? runtimeRoot = null;
        bool noRuntime = false;
        bool yes = false;
        string format = "text";
        while (index < args.Length)
        {
            string option = args[index++];
            switch (option)
            {
                case "--yes" when !yes: yes = true; break;
                case "--no-runtime" when !noRuntime && runtimeRoot is null: noRuntime = true; break;
                case "--config" when config is null: config = Value(option); break;
                case "--lang" when language is null: language = Value(option); break;
                case "--transport" when command == AppCommand.Serve && transport is null:
                    transport = Value(option);
                    if (transport is not ("stdio" or "http")) throw new CommandLineException("Transport must be stdio or http.");
                    break;
                case "--path" when executablePath is null: executablePath = Value(option); break;
                case "--runtime-root" when runtimeRoot is null && !noRuntime: runtimeRoot = Value(option); break;
                case "--format" when command == AppCommand.SandboxShow:
                    format = Value(option);
                    if (format is not ("text" or "json")) throw new CommandLineException("Format must be text or json.");
                    break;
                default: throw new CommandLineException($"Unknown, duplicate, or invalid option '{option}' for this command.");
            }
        }
        bool management = command is not (AppCommand.Serve or AppCommand.Check);
        if (management && config is null) throw new CommandLineException("--config <file> is required for executable and sandbox commands.");
        if (yes && command is not (AppCommand.ExecutableApprove or AppCommand.ExecutableRevoke or AppCommand.SandboxConfirm))
            throw new CommandLineException("--yes is valid only for approve, revoke, and confirm.");
        return new(command, config, language, transport, alias, executablePath, runtimeRoot, noRuntime, yes, format);

        string Value(string optionName)
        {
            if (index >= args.Length) throw new CommandLineException($"Missing value for '{optionName}'.");
            return args[index++];
        }
    }
}

public sealed class CommandLineException(string message) : Exception(message);