use std::ffi::OsString;
use std::path::PathBuf;
use a3s::components::ComponentId;
use clap::{Args, Parser, Subcommand, ValueEnum};
mod code;
pub(crate) use code::*;
mod admin;
pub(crate) use admin::*;
#[derive(Debug, Parser)]
#[command(
name = "a3s",
version,
about = "A3S agent platform CLI",
propagate_version = true,
disable_help_subcommand = true
)]
pub(crate) struct Cli {
#[arg(short = 'C', long, global = true, value_name = "PATH")]
pub directory: Option<PathBuf>,
#[arg(long, global = true, value_name = "PATH")]
pub config: Option<PathBuf>,
#[arg(long, global = true, value_enum, default_value_t = OutputMode::Human)]
pub output: OutputMode,
#[arg(long, global = true, conflicts_with = "output")]
pub json: bool,
#[arg(short, long, global = true, conflicts_with = "verbose")]
pub quiet: bool,
#[arg(short, long, global = true, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(long, global = true, value_enum, default_value_t = ColorMode::Auto)]
pub color: ColorMode,
#[arg(long, global = true)]
pub no_progress: bool,
#[arg(long, global = true)]
pub offline: bool,
#[arg(long, global = true)]
pub non_interactive: bool,
#[command(subcommand)]
pub command: Option<RootCommand>,
}
impl Cli {
pub(crate) fn output_mode(&self) -> OutputMode {
if self.json {
OutputMode::Json
} else {
self.output
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
pub(crate) enum OutputMode {
#[default]
Human,
Json,
Jsonl,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
pub(crate) enum ColorMode {
#[default]
Auto,
Always,
Never,
}
#[derive(Debug, Subcommand)]
pub(crate) enum RootCommand {
Code(CodeArgs),
Web(WebArgs),
Top(TopArgs),
Box(ProxyArgs),
Compose(ProxyArgs),
Up(ProxyArgs),
Down(ProxyArgs),
Ps(ProxyArgs),
Logs(ProxyArgs),
Bench(ProxyArgs),
Search(ProxyArgs),
Use(ProxyArgs),
Auth(AuthArgs),
Model(ModelArgs),
Config(ConfigArgs),
List(ListArgs),
Info(InfoArgs),
Install(InstallArgs),
Upgrade(UpgradeArgs),
Uninstall(UninstallArgs),
Doctor(DoctorArgs),
Registry(RegistryArgs),
Cache(CacheArgs),
#[command(name = "self")]
Self_(SelfArgs),
Version,
Completion(CompletionArgs),
Help(HelpArgs),
#[command(name = "update", hide = true)]
LegacyUpdate(PassthroughArgs),
}
#[derive(Clone, Debug, Args)]
#[command(trailing_var_arg = true, disable_help_flag = true)]
pub(crate) struct PassthroughArgs {
#[arg(allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[derive(Clone, Debug, Args)]
#[command(
trailing_var_arg = true,
disable_help_flag = true,
disable_version_flag = true
)]
pub(crate) struct ProxyArgs {
#[arg(allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[derive(Clone, Debug, Args)]
pub(crate) struct WebArgs {
#[command(subcommand)]
pub command: Option<WebCommand>,
#[command(flatten)]
pub shortcut: WebStartArgs,
}
#[derive(Clone, Debug, Subcommand)]
pub(crate) enum WebCommand {
Start(WebStartArgs),
Stop(WebTargetArgs),
Status(WebTargetArgs),
Logs(WebLogsArgs),
Open(WebTargetArgs),
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct WebStartArgs {
#[arg(short = 'd', long = "detach")]
pub detach: bool,
#[arg(long)]
pub replace: bool,
#[arg(long, value_name = "HOST")]
pub host: Option<String>,
#[arg(long, value_name = "PORT")]
pub port: Option<u16>,
#[arg(short = 'w', long = "workspace", value_name = "PATH", hide = true)]
pub legacy_workspace: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
pub web_dir: Option<PathBuf>,
#[arg(long)]
pub api_only: bool,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct WebTargetArgs {
#[arg(short = 'w', long = "workspace", value_name = "PATH", hide = true)]
pub legacy_workspace: Option<PathBuf>,
}
#[derive(Clone, Debug, Args)]
pub(crate) struct WebLogsArgs {
#[command(flatten)]
pub target: WebTargetArgs,
#[arg(short, long)]
pub follow: bool,
#[arg(short = 'n', long, default_value_t = 100)]
pub lines: usize,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct TopArgs {
#[arg(long, value_name = "CONTAINER", conflicts_with = "legacy_container")]
pub container: Option<String>,
#[arg(long, value_enum, conflicts_with = "legacy_view")]
pub view: Option<TopView>,
#[arg(long, value_enum)]
pub connector: Option<TopConnector>,
#[arg(short = 'a', long, alias = "active-only", conflicts_with = "all")]
pub active: bool,
#[arg(long, conflicts_with = "active")]
pub all: bool,
#[arg(short, long, value_name = "TEXT")]
pub filter: Option<String>,
#[arg(short, long, value_enum)]
pub sort: Option<TopSort>,
#[arg(short, long)]
pub reverse: bool,
#[arg(long, value_enum)]
pub risk: Option<TopRisk>,
#[arg(long, value_enum)]
pub kind: Option<TopEventKind>,
#[arg(long)]
pub watch: bool,
#[arg(long, value_name = "DURATION")]
pub interval: Option<String>,
#[arg(long, value_name = "COUNT", requires = "watch")]
pub count: Option<usize>,
#[arg(long, alias = "compact-columns")]
pub compact: bool,
#[arg(long)]
pub no_header: bool,
#[arg(short, long)]
pub invert: bool,
#[arg(value_name = "LEGACY_CONTAINER", hide = true)]
pub legacy_container: Option<String>,
#[arg(long, hide = true, group = "legacy_view")]
pub agents: bool,
#[arg(long = "sessions", hide = true, group = "legacy_view")]
pub view_sessions: bool,
#[arg(long = "containers", hide = true, group = "legacy_view")]
pub view_containers: bool,
#[arg(long = "processes", hide = true, group = "legacy_view")]
pub view_processes: bool,
#[arg(long = "events", hide = true, group = "legacy_view")]
pub view_events: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum TopView {
Agents,
Sessions,
Containers,
Processes,
Events,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum TopConnector {
#[value(name = "a3s-box")]
A3sBox,
Docker,
Runc,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum TopSort {
Cpu,
Mem,
Net,
Block,
Pids,
State,
Id,
Uptime,
Name,
Tokens,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum TopRisk {
All,
Medium,
High,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum TopEventKind {
All,
Tool,
Security,
File,
Egress,
Llm,
Other,
}
#[derive(Debug, Args)]
#[command(subcommand_required = true, arg_required_else_help = true)]
pub(crate) struct AuthArgs {
#[command(subcommand)]
pub command: AuthCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum AuthCommand {
List,
Status(AuthProviderArgs),
Login(AuthLoginArgs),
Logout(AuthProviderArgs),
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct AuthProviderArgs {
#[arg(value_name = "PROVIDER", default_value = "os", value_parser = ["os"])]
pub provider: String,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct AuthLoginArgs {
#[arg(value_name = "PROVIDER")]
pub provider_or_legacy: Option<OsString>,
#[arg(long, conflicts_with = "token_file")]
pub token_stdin: bool,
#[arg(long, value_name = "PATH", conflicts_with = "token_stdin")]
pub token_file: Option<PathBuf>,
#[arg(value_name = "LEGACY_TOKEN", hide = true)]
pub legacy_values: Vec<OsString>,
}
#[derive(Debug, Args)]
#[command(subcommand_required = true, arg_required_else_help = true)]
pub(crate) struct ModelArgs {
#[command(subcommand)]
pub command: ModelCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum ModelCommand {
List,
Current,
Use(ModelUseArgs),
Reset(ModelScopeArgs),
}
#[derive(Clone, Debug, Args)]
pub(crate) struct ModelUseArgs {
#[arg(value_name = "PROVIDER/MODEL")]
pub model: String,
#[command(flatten)]
pub target: ModelScopeArgs,
}
#[derive(Clone, Debug, Args)]
pub(crate) struct ModelScopeArgs {
#[arg(long, value_enum, default_value_t = ConfigScope::User)]
pub scope: ConfigScope,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
pub(crate) enum ConfigScope {
Workspace,
#[default]
User,
}
#[derive(Clone, Debug, Args)]
pub(crate) struct ConfigScopeArgs {
#[arg(long, value_enum, default_value_t = ConfigScope::User)]
pub scope: ConfigScope,
}
#[derive(Debug, Args)]
#[command(subcommand_required = true, arg_required_else_help = true)]
pub(crate) struct ConfigArgs {
#[command(subcommand)]
pub command: ConfigCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum ConfigCommand {
Path,
Paths,
Show,
Init(ConfigInitArgs),
Edit(ConfigScopeArgs),
Validate(ConfigValidateArgs),
}
#[derive(Clone, Debug, Args)]
pub(crate) struct ConfigInitArgs {
#[arg(long, value_enum, default_value_t = ConfigScope::User)]
pub scope: ConfigScope,
#[arg(long)]
pub force: bool,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct ConfigValidateArgs {
#[arg(value_name = "PATH")]
pub path: Option<PathBuf>,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct ListArgs {
#[arg(long, conflicts_with = "available")]
pub installed: bool,
#[arg(long, conflicts_with = "installed")]
pub available: bool,
#[arg(long)]
pub updates: bool,
#[arg(long, value_enum)]
pub kind: Option<ComponentKindArg>,
}
#[derive(Clone, Debug, Default, Args)]
#[command(disable_version_flag = true)]
pub(crate) struct InstallArgs {
#[arg(value_name = "COMPONENT")]
pub components: Vec<ComponentId>,
#[arg(long, value_name = "VERSION")]
pub version: Option<String>,
#[arg(long, value_name = "SOURCE")]
pub source: Option<String>,
#[arg(long, value_enum, default_value_t = ReleaseChannelArg::Stable)]
pub channel: ReleaseChannelArg,
#[arg(long, value_enum, default_value_t = InstallScopeArg::User)]
pub scope: InstallScopeArg,
#[arg(long = "from", value_name = "PATH")]
pub package: Option<PathBuf>,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub migrate: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(
long,
value_name = "SHA256",
conflicts_with = "dry_run",
value_parser = parse_plan_digest
)]
pub plan_digest: Option<String>,
#[arg(long)]
pub allow_unsigned: bool,
#[arg(long)]
pub yes: bool,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct UpgradeArgs {
#[arg(value_name = "COMPONENT", conflicts_with = "all")]
pub components: Vec<ComponentId>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(
long,
value_name = "SHA256",
conflicts_with = "dry_run",
value_parser = parse_plan_digest
)]
pub plan_digest: Option<String>,
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct UninstallArgs {
#[arg(value_name = "COMPONENT", required = true)]
pub components: Vec<ComponentId>,
#[arg(long)]
pub cascade: bool,
#[arg(long)]
pub purge: bool,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(
long,
value_name = "SHA256",
conflicts_with = "dry_run",
value_parser = parse_plan_digest
)]
pub plan_digest: Option<String>,
}
fn parse_plan_digest(value: &str) -> Result<String, String> {
if value.len() != 64
|| !value
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
{
return Err("expected exactly 64 lowercase hexadecimal characters".to_string());
}
Ok(value.to_string())
}
#[derive(Debug, Args)]
#[command(subcommand_required = true, arg_required_else_help = true)]
pub(crate) struct SelfArgs {
#[command(subcommand)]
pub command: SelfCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum SelfCommand {
Update(SelfUpdateArgs),
}
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct SelfUpdateArgs {
#[arg(long)]
pub check: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub yes: bool,
}