summaryrefslogtreecommitdiff
path: root/src/Agent.App/Cli/CommandLine.cs
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-13 19:15:01 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-13 19:15:01 +0900
commit36ae1a4cba791ec633a81b38b1d8345af8487276 (patch)
tree91cef96efac7eb8c2754658ce5af5181b1a15898 /src/Agent.App/Cli/CommandLine.cs
parent76fdcb43585a4c4d288c1026d318a0ddbe3e26ab (diff)
feat!: unify sandbox configuration and permission application
Replace the split executable approval and sandbox resolution workflows with schema_version 2 and a YAML-driven execution.entries model. Resolve workspace, mount, executable, runtime, network, and resource settings into one config-scoped Applied Configuration snapshot protected by a user-only DACL and written with atomic replacement. Reduce the public CLI to implicit server startup, show, and apply. Remove serve/check, executable and sandbox subcommands, and configuration override flags including --config, --lang, --transport, --path, and --runtime-root. Add migration diagnostics for every removed schema and command surface, and keep server preflight mandatory for both stdio and HTTP. Update effective-policy compilation to validate the applied fingerprint, resolved paths, and executable hashes before startup. Preserve fail-closed behavior while allowing execution entries and runtime roots inside a development workspace to inherit that workspace capability without creating an impossible narrower ACL boundary. Rewrite the README around the consolidated permission matrix and document configuration, application, store location, startup, transport-specific elevation, Python 3.14, PowerShell, and cmd usage. Align specifications, design, implementation plan, traceability, audit report, localization, publish scripts, and operational verification with the breaking schema. Verification: Release build completed with zero warnings and errors; Agent.Core.Tests 104/104 and Agent.Planner.Tests 10/10 passed; administrator Windows Security/Recovery 56/56 and README operational 19/19 passed with no skips; published HTTP MCP completed a cmd/curl/venv-Python task with network deny and zero ACL, WFP, journal, backup, or process residue.
Diffstat (limited to 'src/Agent.App/Cli/CommandLine.cs')
-rw-r--r--src/Agent.App/Cli/CommandLine.cs110
1 files changed, 27 insertions, 83 deletions
diff --git a/src/Agent.App/Cli/CommandLine.cs b/src/Agent.App/Cli/CommandLine.cs
index 3b3fde1..b2364f4 100644
--- a/src/Agent.App/Cli/CommandLine.cs
+++ b/src/Agent.App/Cli/CommandLine.cs
@@ -2,103 +2,47 @@ namespace Agent.App.Cli;
public enum AppCommand
{
- Serve,
- Check,
- ExecutableResolve,
- ExecutableApprove,
- ExecutableList,
- ExecutableRevoke,
- SandboxResolve,
- SandboxConfirm,
- SandboxShow
+ Start,
+ Apply,
+ Show
}
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")
+ bool Yes = false)
{
+ private static readonly HashSet<string> RemovedCommands = new(StringComparer.Ordinal)
+ {
+ "serve", "check", "executable", "sandbox"
+ };
+
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'.");
+ if (args.Length == 0) return new(AppCommand.Start, null);
+ if (RemovedCommands.Contains(args[0]))
+ throw new CommandLineException($"'{args[0]}' was removed in schema_version 2. Use 'agent-mcp', 'agent-mcp apply', or 'agent-mcp show'.");
+ if (args.Any(static value => value is "--config" or "--lang" or "--transport" or "--path" or "--runtime-root" or "--no-runtime" or "--format"))
+ throw new CommandLineException("Configuration override options were removed in schema_version 2. Put values in YAML and pass the config path as a positional argument.");
+ AppCommand command = args[0] switch
+ {
+ "apply" => AppCommand.Apply,
+ "show" => AppCommand.Show,
+ _ when !args[0].StartsWith("--", StringComparison.Ordinal) => AppCommand.Start,
+ _ => throw new CommandLineException($"Unknown option '{args[0]}'.")
+ };
+ int index = command == AppCommand.Start ? 0 : 1;
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++];
+ string value = args[index++];
+ if (value == "--yes" && command == AppCommand.Apply && !yes) yes = true;
+ else if (!value.StartsWith("--", StringComparison.Ordinal) && config is null) config = value;
+ else throw new CommandLineException($"Unknown, duplicate, or invalid argument '{value}' for this command.");
}
+ return new(command, config, yes);
}
}