use aleph_sdk::aggregate_models::corechannel::NodeHash;
use aleph_sdk::credit::PriceSource;
use aleph_types::chain::Address;
use aleph_types::item_hash::ItemHash;
use clap::{Args, Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
use url::Url;
use crate::common::resolve_address;
fn parse_address(s: &str) -> Result<Address, String> {
resolve_address(s).map_err(|e| e.to_string())
}
fn parse_price_source(s: &str) -> Result<PriceSource, String> {
s.parse::<PriceSource>().map_err(|e| e.to_string())
}
pub fn parse_size_to_mib(s: &str) -> Result<u64, String> {
let s = s.trim();
let unit_start = s
.find(|c: char| c.is_alphabetic())
.ok_or_else(|| format!("missing unit in size '{s}' (use e.g. 20GB, 1024MB, 1TiB)"))?;
let (num_str, unit) = s.split_at(unit_start);
let value: f64 = num_str
.trim()
.parse()
.map_err(|_| format!("invalid number in size '{s}'"))?;
if value < 0.0 {
return Err(format!("size cannot be negative: '{s}'"));
}
let mib = match unit.to_lowercase().as_str() {
"mib" => value,
"gib" => value * 1024.0,
"tib" => value * 1024.0 * 1024.0,
"mb" => value * 1_000_000.0 / 1_048_576.0,
"gb" => value * 1_000_000_000.0 / 1_048_576.0,
"tb" => value * 1_000_000_000_000.0 / 1_048_576.0,
_ => {
return Err(format!(
"unknown size unit '{unit}' (use MB, GB, TB, MiB, GiB, TiB)"
));
}
};
let mib_rounded = mib.round() as u64;
if mib_rounded == 0 {
return Err(format!("size too small: '{s}' rounds to 0 MiB"));
}
Ok(mib_rounded)
}
#[derive(Clone, Debug)]
pub enum ImageRef {
Preset(String),
Hash(ItemHash),
}
pub fn parse_image_ref(s: &str) -> Result<ImageRef, String> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err("image cannot be empty".to_string());
}
match ItemHash::try_from(trimmed) {
Ok(h) => Ok(ImageRef::Hash(h)),
Err(_) => Ok(ImageRef::Preset(trimmed.to_string())),
}
}
const LONG_VERSION: &str = concat!(
env!("CARGO_PKG_VERSION"),
" (",
env!("ALEPH_GIT_COMMIT"),
" ",
env!("ALEPH_COMMIT_DATE"),
")"
);
#[derive(Parser)]
#[command(name = "aleph", version, long_version = LONG_VERSION, about = "Aleph CLI")]
pub struct Cli {
#[arg(long)]
pub ccn: Option<String>,
#[arg(long, global = true)]
pub json: bool,
#[arg(long)]
pub network: Option<String>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Account {
#[clap(subcommand)]
command: AccountCommand,
},
Aggregate {
#[clap(subcommand)]
command: AggregateCommand,
},
Authorization {
#[clap(subcommand)]
command: AuthorizationCommand,
},
Config {
#[clap(subcommand)]
command: ConfigCommand,
},
Domain {
#[clap(subcommand)]
command: DomainCommand,
},
File {
#[clap(subcommand)]
command: FileCommand,
},
Instance {
#[clap(subcommand)]
command: InstanceCommand,
},
Message {
#[clap(subcommand)]
command: MessageCommand,
},
Node {
#[clap(subcommand)]
command: NodeCommand,
},
Post {
#[clap(subcommand)]
command: PostCommand,
},
Program {
#[clap(subcommand)]
command: ProgramCommand,
},
Credit {
#[clap(subcommand)]
command: CreditCommand,
},
Token {
#[clap(subcommand)]
command: TokenCommand,
},
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Website {
#[clap(subcommand)]
command: WebsiteCommand,
},
}
#[derive(Subcommand)]
pub enum MessageCommand {
#[command(long_about = "\
Forget messages on the network. Two scopes are supported:
<HASHES>... Forget specific messages, one per item hash. Each hash \
identifies a single message (POST, AGGREGATE element, STORE, PROGRAM, or \
INSTANCE). The network cascades the appropriate cleanup based on the \
target's type: forgetting a STORE releases the file pin, forgetting a POST \
also forgets its amends, forgetting a PROGRAM/INSTANCE tears down its VM, \
and forgetting an AGGREGATE element rebuilds the aggregate from the \
elements that remain.
--aggregates <HASH>... Forget an entire aggregate, identified by the \
item hash of any of its element messages. This forgets *every* AGGREGATE \
message sent by the same address under the same key — not just the one \
hash you passed. Use this when you want the whole aggregate gone, not just \
one update to it.
Examples:
# Forget two specific messages
aleph message forget abc123... def456...
# Forget the entire aggregate keyed at the (sender, key) of this element
aleph message forget --aggregates abc123...
# Combine: forget some specific messages AND wipe an aggregate
aleph message forget abc123... --aggregates def456... --reason \"superseded\"
Forget is irreversible. You can only forget messages your own address owns \
(or that you have an authorization to forget on behalf of).")]
Forget(ForgetArgs),
Get(GetMessageArgs),
List(Box<MessageListArgs>),
Retry(RetryArgs),
Sync(Box<SyncArgs>),
}
#[derive(Args)]
pub struct SyncArgs {
#[arg(long)]
pub source: String,
#[arg(long)]
pub target: String,
#[arg(long, default_value = "200")]
pub count: u32,
#[arg(long)]
pub dry_run: bool,
#[command(flatten)]
pub filter: MessageFilterCli,
}
#[derive(Args)]
pub struct GetMessageArgs {
pub item_hash: ItemHash,
}
#[derive(Args)]
pub struct RetryArgs {
pub item_hash: ItemHash,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Subcommand)]
pub enum PostCommand {
Amend(PostAmendArgs),
Create(PostCreateArgs),
List(Box<PostListArgs>),
}
#[derive(Args)]
pub struct PostListArgs {
#[arg(long, default_value = "1")]
pub api_version: u8,
#[command(flatten)]
pub filter: PostFilterCli,
}
use aleph_sdk::client::{MessageFilter, PostFilter, SortBy, SortOrder};
use aleph_types::message::{MessageStatus, MessageType};
use aleph_types::timestamp::Timestamp;
use chrono::{DateTime, FixedOffset, Utc};
use std::str::FromStr;
fn parse_timestamp(s: &str) -> Result<Timestamp, String> {
if let Ok(timestamp) = s.parse::<f64>() {
return Ok(Timestamp::from(timestamp));
}
let timestamp = DateTime::<FixedOffset>::from_str(s)
.map_err(|e| e.to_string())?
.timestamp();
Ok(Timestamp::from(timestamp as f64))
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum MessageTypeCli {
Aggregate,
Forget,
Instance,
Program,
Post,
Store,
}
impl From<MessageTypeCli> for MessageType {
fn from(v: MessageTypeCli) -> Self {
match v {
MessageTypeCli::Aggregate => MessageType::Aggregate,
MessageTypeCli::Forget => MessageType::Forget,
MessageTypeCli::Instance => MessageType::Instance,
MessageTypeCli::Post => MessageType::Post,
MessageTypeCli::Program => MessageType::Program,
MessageTypeCli::Store => MessageType::Store,
}
}
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum MessageStatusCli {
Pending,
Processed,
Removing,
Removed,
Forgotten,
}
impl From<MessageStatusCli> for MessageStatus {
fn from(v: MessageStatusCli) -> Self {
match v {
MessageStatusCli::Pending => MessageStatus::Pending,
MessageStatusCli::Processed => MessageStatus::Processed,
MessageStatusCli::Removing => MessageStatus::Removing,
MessageStatusCli::Removed => MessageStatus::Removed,
MessageStatusCli::Forgotten => MessageStatus::Forgotten,
}
}
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum SortByCli {
Time,
TxTime,
}
impl From<SortByCli> for SortBy {
fn from(v: SortByCli) -> Self {
match v {
SortByCli::Time => SortBy::Time,
SortByCli::TxTime => SortBy::TxTime,
}
}
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum SortOrderCli {
Asc,
Desc,
}
impl From<SortOrderCli> for SortOrder {
fn from(v: SortOrderCli) -> Self {
match v {
SortOrderCli::Asc => SortOrder::Asc,
SortOrderCli::Desc => SortOrder::Desc,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
#[clap(rename_all = "lowercase")]
pub enum FrameworkCli {
None,
Nextjs,
React,
Vue,
Gatsby,
Svelte,
Nuxt,
Angular,
Other,
}
impl std::fmt::Display for FrameworkCli {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
FrameworkCli::None => "none",
FrameworkCli::Nextjs => "nextjs",
FrameworkCli::React => "react",
FrameworkCli::Vue => "vue",
FrameworkCli::Gatsby => "gatsby",
FrameworkCli::Svelte => "svelte",
FrameworkCli::Nuxt => "nuxt",
FrameworkCli::Angular => "angular",
FrameworkCli::Other => "other",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn framework_display_round_trip() {
assert_eq!(FrameworkCli::Nextjs.to_string(), "nextjs");
assert_eq!(FrameworkCli::Other.to_string(), "other");
}
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum ChainCli {
Arb,
Aurora,
Avax,
Base,
Blast,
Bob,
Bsc,
Cyber,
Eth,
Etherlink,
Frax,
Hype,
Ink,
Lens,
Linea,
Lisk,
Metis,
Mode,
Op,
Pol,
Stt,
Sonic,
Unichain,
Wld,
Zora,
Sol,
Es,
}
impl From<ChainCli> for aleph_types::chain::Chain {
fn from(v: ChainCli) -> Self {
use aleph_types::chain::Chain;
match v {
ChainCli::Arb => Chain::Arbitrum,
ChainCli::Aurora => Chain::Aurora,
ChainCli::Avax => Chain::Avax,
ChainCli::Base => Chain::Base,
ChainCli::Blast => Chain::Blast,
ChainCli::Bob => Chain::Bob,
ChainCli::Bsc => Chain::Bsc,
ChainCli::Cyber => Chain::Cyber,
ChainCli::Eth => Chain::Ethereum,
ChainCli::Etherlink => Chain::Etherlink,
ChainCli::Frax => Chain::Fraxtal,
ChainCli::Hype => Chain::Hype,
ChainCli::Ink => Chain::Ink,
ChainCli::Lens => Chain::Lens,
ChainCli::Linea => Chain::Linea,
ChainCli::Lisk => Chain::Lisk,
ChainCli::Metis => Chain::Metis,
ChainCli::Mode => Chain::Mode,
ChainCli::Op => Chain::Optimism,
ChainCli::Pol => Chain::Pol,
ChainCli::Stt => Chain::Somnia,
ChainCli::Sonic => Chain::Sonic,
ChainCli::Unichain => Chain::Unichain,
ChainCli::Wld => Chain::Worldchain,
ChainCli::Zora => Chain::Zora,
ChainCli::Sol => Chain::Sol,
ChainCli::Es => Chain::Eclipse,
}
}
}
#[derive(Debug, Clone, Args)]
pub struct IdentityArgs {
#[arg(long)]
pub account: Option<String>,
#[arg(long)]
pub private_key: Option<String>,
#[arg(long, value_enum)]
pub chain: Option<ChainCli>,
}
#[derive(Debug, Clone, Args)]
pub struct SigningArgs {
#[command(flatten)]
pub identity: IdentityArgs,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Debug, Clone, Args)]
pub struct MessageFilterCli {
#[arg(long, value_delimiter = ',', value_enum)]
pub message_type: Option<MessageTypeCli>,
#[arg(long, value_delimiter = ',', value_enum)]
pub message_types: Option<Vec<MessageTypeCli>>,
#[arg(long, value_delimiter = ',')]
pub content_types: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub content_keys: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub content_hashes: Option<Vec<ItemHash>>,
#[arg(long, value_delimiter = ',')]
pub refs: Option<Vec<String>>,
#[arg(long, value_delimiter = ',', value_parser = parse_address)]
pub addresses: Option<Vec<Address>>,
#[arg(long, value_delimiter = ',', value_parser = parse_address)]
pub owners: Option<Vec<Address>>,
#[arg(long, value_delimiter = ',')]
pub tags: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub hashes: Option<Vec<ItemHash>>,
#[arg(long, value_delimiter = ',')]
pub channels: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub chains: Option<Vec<String>>,
#[arg(long, value_parser = parse_timestamp)]
pub start_date: Option<Timestamp>,
#[arg(long, value_parser = parse_timestamp)]
pub end_date: Option<Timestamp>,
#[arg(long, value_enum)]
pub sort_by: Option<SortByCli>,
#[arg(long, value_enum)]
pub sort_order: Option<SortOrderCli>,
#[arg(long, value_delimiter = ',')]
pub message_statuses: Option<Vec<MessageStatusCli>>,
}
#[derive(Debug, Clone, Args)]
pub struct MessageListArgs {
#[arg(long, default_value = "200")]
pub count: u32,
#[command(flatten)]
pub filter: MessageFilterCli,
}
impl From<MessageFilterCli> for MessageFilter {
fn from(c: MessageFilterCli) -> Self {
MessageFilter {
message_type: c.message_type.map(Into::into),
message_types: c
.message_types
.map(|v| v.into_iter().map(Into::into).collect()),
content_types: c.content_types,
content_keys: c.content_keys,
content_hashes: c.content_hashes,
refs: c.refs,
addresses: c.addresses,
owners: c.owners,
tags: c.tags,
hashes: c.hashes,
channels: c.channels,
chains: c.chains,
start_date: c.start_date,
end_date: c.end_date,
sort_by: c.sort_by.map(Into::into),
sort_order: c.sort_order.map(Into::into),
message_statuses: c
.message_statuses
.map(|v| v.into_iter().map(Into::into).collect()),
}
}
}
#[derive(Debug, Clone, Args)]
pub struct PostFilterCli {
#[arg(long, value_delimiter = ',', value_parser = parse_address)]
pub addresses: Option<Vec<Address>>,
#[arg(long, value_delimiter = ',')]
pub hashes: Option<Vec<ItemHash>>,
#[arg(long, value_delimiter = ',')]
pub refs: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub post_types: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub tags: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
pub channels: Option<Vec<String>>,
#[arg(long, value_parser = parse_timestamp)]
pub start_date: Option<Timestamp>,
#[arg(long, value_parser = parse_timestamp)]
pub end_date: Option<Timestamp>,
#[arg(long, value_enum)]
pub sort_by: Option<SortByCli>,
#[arg(long, value_enum)]
pub sort_order: Option<SortOrderCli>,
#[arg(long, default_value = "200")]
pub pagination: u32,
#[arg(long, default_value = "1")]
pub page: u32,
}
impl From<PostFilterCli> for PostFilter {
fn from(c: PostFilterCli) -> Self {
PostFilter {
addresses: c.addresses,
hashes: c.hashes,
refs: c.refs,
post_types: c.post_types,
tags: c.tags,
channels: c.channels,
start_date: c.start_date,
end_date: c.end_date,
sort_by: c.sort_by.map(Into::into),
sort_order: c.sort_order.map(Into::into),
}
}
}
#[derive(Args)]
pub struct PostCreateArgs {
#[arg(long = "type")]
pub post_type: String,
#[arg(long)]
pub content: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct PostAmendArgs {
#[arg(long = "ref")]
pub reference: ItemHash,
#[arg(long)]
pub content: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
pub enum AggregateCommand {
Create(AggregateCreateArgs),
Edit(AggregateEditArgs),
Unset(AggregateUnsetArgs),
Get(AggregateGetArgs),
List(AggregateListArgs),
Forget(AggregateForgetArgs),
}
#[derive(Args)]
pub struct AggregateGetArgs {
pub key: String,
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct AggregateListArgs {
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct AggregateForgetArgs {
pub hashes: Vec<ItemHash>,
#[arg(long)]
pub reason: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct AggregateCreateArgs {
#[arg(long)]
pub key: String,
#[arg(long)]
pub content: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct AggregateEditArgs {
#[arg(long)]
pub key: String,
#[arg(long)]
pub subkey: Option<String>,
#[arg(long)]
pub content: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct AggregateUnsetArgs {
#[arg(long)]
pub key: String,
#[arg(long, value_delimiter = ',')]
pub subkey: Vec<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct ForgetArgs {
pub hashes: Vec<ItemHash>,
#[arg(long, value_delimiter = ',')]
pub aggregates: Option<Vec<ItemHash>>,
#[arg(long)]
pub reason: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum NodeCommand {
Amend(AmendNodeArgs),
CreateCcn(CreateCcnArgs),
CreateCrn(CreateCrnArgs),
Drop(DropNodeArgs),
Link(LinkCrnArgs),
List(NodeListArgs),
Stake(StakeArgs),
Unlink(UnlinkCrnArgs),
Unstake(UnstakeArgs),
}
#[derive(Debug, Clone, Args)]
#[command(group = clap::ArgGroup::new("scope").args(["address", "all"]))]
pub struct NodeListArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long, value_enum, rename_all = "lowercase")]
pub r#type: Option<NodeTypeCli>,
#[arg(long)]
pub corechannel_address: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum NodeTypeCli {
Ccn,
Crn,
}
#[derive(Args)]
pub struct CreateCcnArgs {
#[arg(value_name = "NAME")]
pub name: String,
#[arg(long)]
pub multiaddress: String,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct CreateCrnArgs {
#[arg(value_name = "NAME")]
pub name: String,
#[arg(long)]
pub url: String,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct LinkCrnArgs {
#[arg(long)]
pub crn: NodeHash,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct UnlinkCrnArgs {
#[arg(long)]
pub crn: NodeHash,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct StakeArgs {
#[arg(long)]
pub node: NodeHash,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct UnstakeArgs {
#[arg(long)]
pub node: NodeHash,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct DropNodeArgs {
#[arg(long)]
pub node: NodeHash,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct AmendNodeArgs {
#[arg(long)]
pub node: NodeHash,
#[arg(long)]
pub name: Option<String>,
#[arg(long)]
pub multiaddress: Option<String>,
#[arg(long)]
pub url: Option<String>,
#[arg(long)]
pub picture: Option<String>,
#[arg(long)]
pub banner: Option<String>,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub reward: Option<String>,
#[arg(long)]
pub stream_reward: Option<String>,
#[arg(long)]
pub manager: Option<String>,
#[arg(long, value_delimiter = ',')]
pub authorized: Option<Vec<String>>,
#[arg(long)]
pub locked: Option<bool>,
#[arg(long)]
pub registration_url: Option<String>,
#[arg(long)]
pub terms_and_conditions: Option<String>,
#[arg(long)]
pub network_tag: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
pub enum AccountCommand {
Alias {
#[clap(subcommand)]
command: AliasCommand,
},
Balance(AccountBalanceArgs),
#[command(long_about = "\
Generate a fresh private key and store it in the OS keychain under the \
given name. The key never touches disk. Use the resulting account by \
passing `--account <NAME>` to signing commands, or set it as the default \
with `aleph account use <NAME>`.
With --encrypted, the key is instead stored on disk as a password-protected \
Ethereum keystore V3 file (EVM chains only). The password is asked once per \
command at signing time; set ALEPH_PASSWORD for non-interactive use.
To import an existing key (private-key, keystore file, or Ledger), use \
`aleph account import` instead.
Examples:
aleph account create alice # EVM (default chain: eth)
aleph account create alice --chain sol # Solana
aleph account create alice --encrypted # password-protected keystore
aleph account use alice # set as default for signing")]
Create(AccountCreateArgs),
Remove(AccountRemoveArgs),
Export(AccountExportArgs),
#[command(long_about = "\
Import an existing key into the OS keychain under the given name. Three \
sources are supported, mutually exclusive:
--private-key <HEX> Hex-encoded private key on the command line. If \
omitted (and no other source given), the key is read from stdin so it does \
not appear in shell history.
--from-file <PATH> Read from a file containing a raw 32-byte binary \
key, a hex-encoded text key, or an Ethereum keystore V3 file (detected \
automatically; you will be asked for the file's password, and the file is \
kept encrypted as-is).
--ledger Use a Ledger hardware wallet. Combine with \
`--derivation-path` to override the default BIP44 path, and \
`--ledger-count` to fetch more than the default 5 candidate addresses.
With --encrypted, a raw key is stored on disk as a password-protected \
Ethereum keystore V3 file instead of the OS keychain (EVM chains only). \
For keystore V3 files this is implied: the file is imported as-is and its \
original password is kept.
Examples:
aleph account import alice --private-key 0xabcd1234...
aleph account import alice --from-file ~/keys/alice.key
aleph account import alice --from-file ~/keystore.json # keystore V3
aleph account import alice --private-key 0xab... --encrypted
aleph account import alice --ledger
echo \"0xabcd1234...\" | aleph account import alice # via stdin")]
Import(AccountImportArgs),
List,
Migrate(AccountMigrateArgs),
Show(AccountShowArgs),
#[command(long_about = "\
Update settings of an existing account. Pass the account to update as the \
argument (defaults to the active account), then one or more changes:
--chain <CHAIN> Change the chain used for signing. Only changes within \
the same signature family are allowed (EVM<->EVM, SVM<->SVM); the address is \
derived from the key and is the same across a family. Switching family would \
point at a different address, so import a separate account for that instead.
--name <NAME> Rename the account. Moves the stored key material \
(keyring entry or keystore file) and updates the default pointer if needed.
Examples:
aleph account set my-wallet --chain eth # relabel BASE -> ETH
aleph account set my-wallet --name treasury # rename
aleph account set --chain eth # update the active account")]
Set(AccountSetArgs),
Use(AccountUseArgs),
}
#[derive(Args)]
pub struct AccountCreateArgs {
#[arg(value_name = "NAME")]
pub name: String,
#[arg(long, value_enum, default_value = "eth")]
pub chain: ChainCli,
#[arg(long)]
pub encrypted: bool,
}
#[derive(Args)]
pub struct AccountImportArgs {
#[arg(value_name = "NAME")]
pub name: String,
#[arg(long, value_enum, default_value = "eth")]
pub chain: ChainCli,
#[arg(long, conflicts_with = "ledger")]
pub private_key: Option<String>,
#[arg(long, conflicts_with_all = ["private_key", "ledger"])]
pub from_file: Option<PathBuf>,
#[arg(long, conflicts_with = "ledger")]
pub encrypted: bool,
#[arg(long)]
pub ledger: bool,
#[arg(long, requires = "ledger")]
pub derivation_path: Option<String>,
#[arg(long, requires = "ledger", default_value = "5")]
pub ledger_count: usize,
}
#[derive(Args)]
pub struct AccountMigrateArgs {
#[arg(long)]
pub python_home: Option<PathBuf>,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Args)]
pub struct AccountShowArgs {
pub name: Option<String>,
}
#[derive(Args)]
pub struct AccountSetArgs {
pub account: Option<String>,
#[arg(long)]
pub chain: Option<ChainCli>,
#[arg(long)]
pub name: Option<String>,
}
#[derive(Args)]
pub struct AccountBalanceArgs {
pub address: Option<String>,
}
#[derive(Args)]
pub struct AccountRemoveArgs {
pub name: String,
#[arg(short = 'y', long)]
pub yes: bool,
}
#[derive(Args)]
pub struct AccountUseArgs {
pub name: String,
}
#[derive(Args)]
pub struct AccountExportArgs {
pub name: String,
#[arg(long)]
pub yes: bool,
}
#[derive(Subcommand)]
pub enum AliasCommand {
Add(AliasAddArgs),
List,
Remove(AliasRemoveArgs),
}
#[derive(Args)]
pub struct AliasAddArgs {
pub name: String,
pub address: String,
}
#[derive(Args)]
pub struct AliasRemoveArgs {
pub name: String,
}
#[derive(Subcommand)]
pub enum FileCommand {
#[command(long_about = "\
Delete files by their content hash (IPFS CID or native hex). Each hash is \
resolved to the STORE message that pins the file for the owner, and that \
message is forgotten - releasing the pin.
Use `aleph message forget` instead when you need to forget a specific \
STORE *message* by its item hash (e.g. to remove a duplicate pin while \
keeping the file alive via the others).
Forget is irreversible. You can only delete files owned by your own \
address (or that you have an authorization to forget on behalf of).
Examples:
aleph file delete Qmabc... # IPFS CID
aleph file delete 9675a23e... # native hex
aleph file delete Qmabc... QmDef... --reason \"superseded\"
aleph file delete Qmabc... -y --on-behalf-of 0x...")]
Delete(FileDeleteArgs),
Download(FileDownloadArgs),
List(FileListArgs),
Pin(FilePinArgs),
#[command(long_about = "\
Upload a file (or directory) and create a STORE message announcing it on \
the network. The signed STORE message anchors a content-addressed pin: \
its item hash is the file's hash, and the network keeps the content as \
long as the pin is paid for.
Storage engine defaults to `storage` (Aleph native, ≤ 100 MB) for files \
and `ipfs` for directories. Pass `--storage-engine ipfs` to put a single \
file on IPFS instead. Payment defaults to credits; pass \
`--payment-type hold` to fall back to locked-stake.
Use `--ref <NAME>` to give the file a stable user-defined identifier \
(e.g. `report/latest`) — useful for in-place updates and for downloading \
later via `aleph file download --ref ...`.
Examples:
aleph file upload ./report.pdf
aleph file upload ./website/ # directory → IPFS
aleph file upload ./big.bin --storage-engine ipfs
aleph file upload ./report.pdf --ref reports/q4
aleph file upload ./data.bin --channel my-channel")]
Upload(FileUploadArgs),
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum StorageEngineCli {
Storage,
Ipfs,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum PaymentTypeCli {
Hold,
Credit,
}
#[derive(Args)]
pub struct FileUploadArgs {
pub path: std::path::PathBuf,
#[arg(long, value_enum)]
pub storage_engine: Option<StorageEngineCli>,
#[arg(long, value_enum)]
pub payment_type: Option<PaymentTypeCli>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long = "ref")]
pub reference: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(long, env = "ALEPH_IPFS_GATEWAY")]
pub ipfs_gateway: Option<Url>,
#[arg(long, default_value_t = false)]
pub use_gateway_relay: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct FilePinArgs {
pub item_hash: ItemHash,
#[arg(long, value_enum)]
pub payment_type: Option<PaymentTypeCli>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long = "ref")]
pub reference: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct FileDownloadArgs {
#[arg(conflicts_with_all = ["message_hash", "reference"])]
pub hash: Option<ItemHash>,
#[arg(long, conflicts_with_all = ["hash", "reference"])]
pub message_hash: Option<ItemHash>,
#[arg(long = "ref", conflicts_with_all = ["hash", "message_hash"])]
pub reference: Option<String>,
#[arg(long, requires = "reference")]
pub owner: Option<String>,
#[arg(short, long)]
pub output: Option<std::path::PathBuf>,
#[arg(long)]
pub stdout: bool,
}
#[derive(Args)]
pub struct FileListArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long, default_value = "25")]
pub count: u32,
#[arg(long, value_enum, default_value = "desc")]
pub sort_order: SortOrderCli,
}
#[derive(Args)]
pub struct FileDeleteArgs {
pub hashes: Vec<ItemHash>,
#[arg(long)]
pub reason: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum InstanceCommand {
#[command(long_about = "\
Create a new VM instance. Sizing is specified one of three ways:
--size <SLUG> e.g. `1vcpu-2gb`, `2vcpu-4gb`, \
`4vcpu-8gb`, `8vcpu-16gb`.
--vcpus N --memory <SIZE> --disk-size <SIZE>
Custom sizing. Sizes accept \
human-readable forms like `4GB`, `512MiB`, `1TiB`.
--gpu <MODEL> [...] GPU instance. Use \
`aleph instance price --list-gpus` to list models. `--gpu <MODEL>` alone \
sizes the VM at the GPU's minimum. GPU sizes scale in compute-unit steps \
(1 vCPU + 6 GiB each), so `--size` accepts the minimum or any larger multiple \
(e.g. `4vcpu-24gb`, `5vcpu-30gb`); `--vcpus`/`--memory` work too. `--disk-size` \
is optional for GPU instances.
Required: NAME (positional), `--image`, and at least one \
`--ssh-pubkey-file`. Image accepts a preset name from the network's \
`vm-images` aggregate (e.g. `ubuntu26`, `debian12`) or an item hash \
(hex or IPFS CID).
Pin to a specific compute node with `--crn-hash <HASH>`. For an \
interactive walkthrough that prompts for any missing fields and lets you \
pick a CRN from a list, pass `-i` / `--interactive`.
Volumes can be added with `--persistent-volume`, `--ephemeral-volume`, or \
`--immutable-volume` (each can be repeated).
Examples:
aleph instance create web --image ubuntu26 --size 1vcpu-2gb \\
--ssh-pubkey-file ~/.ssh/id_ed25519.pub
aleph instance create gpu-job --image ubuntu26 --gpu h100 \\
--ssh-pubkey-file ~/.ssh/id_ed25519.pub
aleph instance create db --image ubuntu26 --size 4vcpu-8gb \\
--persistent-volume name=data,mount=/data,size=100GB \\
--ssh-pubkey-file ~/.ssh/id_ed25519.pub
aleph instance create -i web # interactive prompts for everything else")]
Create(InstanceCreateArgs),
#[command(long_about = "\
Delete an instance. Forgets the corresponding INSTANCE message.
This command does ONLY the FORGET. It does NOT:
- erase the VM on the CRN (run `aleph instance erase` first)
- remove port forwards (run `aleph instance port-forward delete`)
- stop a streaming flow (Superfluid flow stays open on PAYG)
Run those subcommands separately if you need that cleanup.
Examples:
aleph instance delete a41fb91c3e68
aleph instance delete a41fb91c3e68 --reason \"decommission\"
aleph instance delete a41fb91c3e68 --dry-run --json
")]
Delete(InstanceDeleteArgs),
Erase(CrnArgs),
List(InstanceListArgs),
Logs(CrnArgs),
#[command(visible_alias = "pfw")]
PortForward {
#[clap(subcommand)]
command: PortForwardCommand,
},
#[command(long_about = "\
Show pricing for an instance configuration.
There are three ways to specify the instance:
1. By size slug: --size 1vcpu-2gb
2. By resources: --vcpus 4 --memory 8GB --disk-size 100GB
3. By GPU model: --gpu h100
GPU instances have a minimum size determined by the model, then scale in \
compute-unit steps (1 vCPU + 6 GiB each). --size accepts the minimum or any \
larger multiple (e.g. 4vcpu-24gb, 5vcpu-30gb); --vcpus/--memory work too. \
GPU and confidential instances use separate pricing tiers and cannot \
be combined.
Examples:
aleph instance price --size 4vcpu-8gb
aleph instance price --vcpus 2 --memory 4GB --disk-size 50GB
aleph instance price --gpu h100
aleph instance price --gpu h100 --size 32vcpu-192gb
aleph instance price --list-gpus # list available GPU models (recommended)
aleph instance price --gpu # same, kept as a shortcut
aleph instance price --size 1vcpu-2gb --confidential")]
Price(InstancePriceArgs),
Reboot(CrnArgs),
#[command(long_about = "\
Reinstall an instance from its original OS image on the CRN.
The rootfs is always erased and reinstalled from the image referenced by the
INSTANCE message. By default ALL persistent data volumes are also erased; pass
--keep-data to wipe only the rootfs and preserve persistent data volumes.
This is irreversible and asks for confirmation unless --yes is given.
Examples:
aleph instance reinstall a41fb91c3e68
aleph instance reinstall a41fb91c3e68 --keep-data
aleph instance reinstall a41fb91c3e68 --crn 9f3e... -y")]
Reinstall(InstanceReinstallArgs),
#[command(long_about = "\
Show details of a single VM instance.
By default, gathers data without authenticated calls: the INSTANCE message
from the CCN, plus scheduler placement and status.
Pass --verbose to additionally reach the allocated CRN for live networking
(IPv4/IPv6, mapped host ports) and pull the owner's port-forwarding
aggregate. These extras add up to three HTTP calls; skip them for a fast
lookup.
For the raw INSTANCE message, use `aleph message get <hash>`.
VM_ID accepts a unique hash prefix (e.g. the 12-char hash shown by `aleph
instance list`); the scheduler matches it server-side.
Examples:
aleph instance show a41fb91c3e68
aleph instance show a41fb91c3e68 --verbose
aleph instance show a41fb91c3e68 --json")]
Show(InstanceShowArgs),
#[command(long_about = "\
Open an SSH session to a dispatched VM instance.
The instance must already be dispatched: the scheduler is queried to find
which CRN owns it, then the CRN's `/about/executions/list` endpoint is
consulted to discover the VM's IPv6 address. SSH is then exec'd with that
target.
Pass `--crn` (a node hash, unique hash prefix or suffix, or URL) to skip scheduler discovery. Extra arguments after `--`
are forwarded verbatim to `ssh` (e.g. to run a remote command).
Examples:
aleph instance ssh <vm-hash>
aleph instance ssh <vm-hash> --user ubuntu --identity ~/.ssh/id_ed25519
aleph instance ssh <vm-hash> -- uptime")]
Ssh(InstanceSshArgs),
Start(CrnStartArgs),
Stop(CrnArgs),
#[command(subcommand)]
Backup(InstanceBackupCommand),
#[command(subcommand)]
Confidential(ConfidentialCommand),
}
#[derive(Args)]
pub struct InstanceListArgs {
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct InstanceShowArgs {
pub vm_id: String,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Args)]
pub struct InstanceDeleteArgs {
pub vm_id: String,
#[arg(long, default_value = "User deletion")]
pub reason: String,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstancePriceArgs {
#[arg(long)]
pub size: Option<String>,
#[arg(long)]
pub vcpus: Option<u32>,
#[arg(long, value_parser = parse_size_to_mib)]
pub memory: Option<u64>,
#[arg(long, value_parser = parse_size_to_mib)]
pub disk_size: Option<u64>,
#[arg(long, num_args = 0..=1, default_missing_value = "")]
pub gpu: Option<String>,
#[arg(long, conflicts_with = "gpu")]
pub list_gpus: bool,
#[arg(long)]
pub confidential: bool,
}
#[derive(Args)]
pub struct InstanceCreateArgs {
#[arg(value_name = "NAME")]
pub name: String,
#[arg(
long,
value_parser = parse_image_ref,
required_unless_present = "interactive"
)]
pub image: Option<ImageRef>,
#[arg(long, value_parser = parse_size_to_mib)]
pub disk_size: Option<u64>,
#[arg(long)]
pub size: Option<String>,
#[arg(long)]
pub vcpus: Option<u32>,
#[arg(long, value_parser = parse_size_to_mib)]
pub memory: Option<u64>,
#[arg(long, required_unless_present = "interactive")]
pub ssh_pubkey_file: Vec<PathBuf>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub persistent_volume: Option<Vec<String>>,
#[arg(long)]
pub ephemeral_volume: Option<Vec<String>>,
#[arg(long)]
pub immutable_volume: Option<Vec<String>>,
#[arg(long)]
pub confidential: bool,
#[arg(long, value_parser = parse_image_ref)]
pub confidential_firmware: Option<ImageRef>,
#[arg(long)]
pub gpu: Option<Vec<String>>,
#[arg(long)]
pub crn_hash: Option<NodeHash>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[arg(short = 'i', long)]
pub interactive: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
pub enum AuthorizationCommand {
Add(AuthorizationAddArgs),
List(AuthorizationListArgs),
Received(AuthorizationReceivedArgs),
Revoke(AuthorizationRevokeArgs),
}
#[derive(Debug, Clone, Args)]
pub struct AuthorizationListArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long)]
pub delegate: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
}
#[derive(Debug, Clone, Args)]
pub struct AuthorizationReceivedArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long)]
pub granter: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
}
#[derive(Debug, Clone, Args)]
pub struct AuthorizationAddArgs {
pub delegate_address: String,
#[arg(long = "delegate-chain", value_enum)]
pub delegate_chain: Option<ChainCli>,
#[arg(long, value_delimiter = ',')]
pub channels: Vec<String>,
#[arg(long, value_delimiter = ',', value_enum)]
pub message_types: Vec<MessageTypeCli>,
#[arg(long, value_delimiter = ',')]
pub post_types: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub aggregate_keys: Vec<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Debug, Clone, Args)]
#[command(group(clap::ArgGroup::new("target").required(true)))]
pub struct AuthorizationRevokeArgs {
#[arg(group = "target")]
pub delegate_address: Option<String>,
#[arg(long, group = "target")]
pub all: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct CrnArgs {
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
pub vm_id: String,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct CrnStartArgs {
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
pub vm_id: String,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstanceReinstallArgs {
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
pub vm_id: String,
#[arg(long)]
pub keep_data: bool,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
pub enum InstanceBackupCommand {
Create(InstanceBackupCreateArgs),
Info(InstanceBackupInfoArgs),
Download(InstanceBackupDownloadArgs),
Delete(InstanceBackupDeleteArgs),
Restore(InstanceBackupRestoreArgs),
}
#[derive(Args)]
pub struct InstanceBackupCreateArgs {
pub vm_id: String,
#[arg(long)]
pub include_volumes: bool,
#[arg(long)]
pub skip_fsfreeze: bool,
#[arg(long)]
pub follow: bool,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstanceBackupInfoArgs {
pub vm_id: String,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstanceBackupDownloadArgs {
pub vm_id_or_url: String,
#[arg(short, long)]
pub output: Option<std::path::PathBuf>,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstanceBackupDeleteArgs {
pub vm_id: String,
pub backup_id: String,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
#[command(group(
clap::ArgGroup::new("restore_source")
.required(true)
.args(["file", "volume_ref"]),
))]
pub struct InstanceBackupRestoreArgs {
pub vm_id: String,
#[arg(short, long, group = "restore_source")]
pub file: Option<std::path::PathBuf>,
#[arg(long, group = "restore_source")]
pub volume_ref: Option<String>,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct InstanceSshArgs {
pub vm_id: String,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[arg(long, default_value = "root")]
pub user: String,
#[arg(long, default_value_t = 22)]
pub port: u16,
#[arg(short = 'i', long)]
pub identity: Option<std::path::PathBuf>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub ssh_args: Vec<String>,
}
#[derive(Subcommand)]
pub enum ConfigCommand {
Ccn {
#[clap(subcommand)]
command: CcnCommand,
},
Network {
#[clap(subcommand)]
command: NetworkCommand,
},
}
#[derive(Subcommand)]
pub enum CcnCommand {
Add(CcnAddArgs),
List(CcnListArgs),
Remove(CcnRemoveArgs),
Show(CcnShowArgs),
Use(CcnUseArgs),
}
#[derive(Args)]
pub struct CcnAddArgs {
pub name: String,
pub url: String,
#[arg(long)]
pub network: Option<String>,
}
#[derive(Args)]
pub struct CcnListArgs {
#[arg(long, conflicts_with = "all")]
pub network: Option<String>,
#[arg(long)]
pub all: bool,
}
#[derive(Args)]
pub struct CcnUseArgs {
pub name: String,
#[arg(long)]
pub network: Option<String>,
}
#[derive(Args)]
pub struct CcnShowArgs {
pub name: Option<String>,
#[arg(long)]
pub network: Option<String>,
}
#[derive(Args)]
pub struct CcnRemoveArgs {
pub name: String,
#[arg(long)]
pub network: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
}
#[derive(Subcommand)]
pub enum NetworkCommand {
Add(NetworkAddArgs),
List,
Remove(NetworkRemoveArgs),
Show(NetworkShowArgs),
Use(NetworkUseArgs),
Set(NetworkSetArgs),
}
#[derive(Args)]
pub struct NetworkEthereumArgs {
#[arg(long)]
pub rpc_url: Option<String>,
#[arg(long)]
pub credit_contract: Option<alloy_primitives::Address>,
#[arg(long)]
pub aleph_token: Option<alloy_primitives::Address>,
#[arg(long)]
pub usdc_token: Option<alloy_primitives::Address>,
#[arg(long, value_parser = parse_price_source)]
pub price_source: Option<aleph_sdk::credit::PriceSource>,
#[arg(long)]
pub explorer_tx_base: Option<String>,
}
#[derive(Args)]
pub struct NetworkAddArgs {
pub name: String,
#[arg(long)]
pub scheduler_url: Option<String>,
#[command(flatten)]
pub ethereum: NetworkEthereumArgs,
}
#[derive(Args)]
pub struct NetworkUseArgs {
pub name: String,
}
#[derive(Args)]
pub struct NetworkShowArgs {
pub name: Option<String>,
}
#[derive(Args)]
pub struct NetworkRemoveArgs {
pub name: String,
#[arg(short = 'y', long)]
pub yes: bool,
}
#[derive(Args)]
pub struct NetworkSetArgs {
#[arg(long)]
pub network: Option<String>,
#[arg(long)]
pub scheduler_url: Option<String>,
#[command(flatten)]
pub ethereum: NetworkEthereumArgs,
}
#[derive(Subcommand)]
pub enum CreditCommand {
#[command(long_about = "\
Buy Aleph credits by transferring ALEPH, USDC, or native ETH from your EVM \
account. ALEPH and USDC are ERC20 transfers; ETH is a plain value transfer to \
the network's credit purchase address. Either way the protocol mints credits \
to your address once the transfer is confirmed.
`--amount` is in human-readable token units (decimals OK), not credits. \
1 USD purchases 1,000,000 credits. Use `aleph account balance` afterwards \
to confirm the credits arrived.
Examples:
aleph credit buy --token aleph --amount 100
aleph credit buy --token usdc --amount 50.5
aleph credit buy --token eth --amount 0.05
aleph credit buy --token usdc --amount 25 --yes # skip confirmation
aleph credit buy --token aleph --amount 10 --rpc-url https://my-node.example")]
Buy(BuyCreditArgs),
#[command(long_about = "\
Transfer credits to another address. `--amount` is the number of credits \
(integer, not tokens) — 1 USD ≈ 1,000,000 credits. The recipient can be a \
hex address, a local account name, or an alias from `aleph account alias \
list`.
Optionally pass `--expiration <RFC3339>` to claw back any unspent portion \
after that time.
Examples:
aleph credit transfer --to bob --amount 1000000
aleph credit transfer --to 0xab12... --amount 500000
aleph credit transfer --to bob --amount 250000 \\
--expiration 2026-12-31T23:59:59Z")]
Transfer(TransferCreditArgs),
History(CreditHistoryArgs),
}
#[derive(Args)]
pub struct CreditHistoryArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long, default_value_t = 1)]
pub page: u32,
#[arg(long, default_value_t = 100)]
pub page_size: u32,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CreditTokenCli {
Aleph,
Usdc,
Eth,
}
#[derive(Args)]
pub struct BuyCreditArgs {
#[arg(long, value_enum)]
pub token: CreditTokenCli,
#[arg(long)]
pub amount: String,
#[arg(long)]
pub rpc_url: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
pub fn parse_rfc3339_utc(s: &str) -> Result<DateTime<Utc>, String> {
DateTime::parse_from_rfc3339(s)
.map(|dt| dt.with_timezone(&Utc))
.map_err(|e| format!("invalid RFC3339 timestamp '{s}': {e}"))
}
#[derive(Args)]
pub struct TransferCreditArgs {
#[arg(long)]
pub to: String,
#[arg(long)]
pub amount: u64,
#[arg(long, value_parser = parse_rfc3339_utc)]
pub expiration: Option<DateTime<Utc>>,
#[arg(long)]
pub channel: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
pub enum TokenCommand {
#[command(long_about = "\
Swap native ETH or USDC for ALEPH, leaving the ALEPH in your wallet.
--amount is in human-readable units of the sell token.
Three venues (--venue):
cow (default): gasless off-chain order filled by solvers; may expire
unfilled on a thin pair. Prints the order UID and exits;
check fill status on the explorer.
uniswap: immediate on-chain swap against Uniswap v3 pools; you pay
gas and the pool fee is embedded in the price. Useful when
a CoW order expired unfilled.
ophis: CoW order routed through Ophis with a 0.10% partner fee;
settles on the same mainnet orderbook as cow.
Examples:
aleph token swap --sell-token usdc --amount 100
aleph token swap --sell-token eth --amount 0.5 --slippage 1.0
aleph token swap --sell-token eth --amount 0.5 --venue uniswap
aleph token swap --sell-token usdc --amount 100 --venue ophis
aleph token swap --sell-token usdc --amount 50 --yes")]
Swap(TokenSwapArgs),
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum SwapTokenCli {
Eth,
Usdc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum SwapVenueCli {
Cow,
Uniswap,
Ophis,
}
#[derive(Args)]
pub struct TokenSwapArgs {
#[arg(long, value_enum)]
pub sell_token: SwapTokenCli,
#[arg(long, value_enum, default_value = "cow")]
pub venue: SwapVenueCli,
#[arg(long)]
pub amount: String,
#[arg(long, default_value_t = 0.5)]
pub slippage: f64,
#[arg(long)]
pub receiver: Option<String>,
#[arg(long, default_value_t = 1200)]
pub valid_for: u32,
#[arg(long)]
pub rpc_url: Option<String>,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum ProgramCommand {
Create(ProgramCreateArgs),
Update(ProgramUpdateArgs),
Delete(ProgramDeleteArgs),
List(ProgramListArgs),
Show(ProgramShowArgs),
}
#[derive(Args)]
pub struct ProgramCreateArgs {
pub path: PathBuf,
pub entrypoint: String,
#[arg(long)]
pub name: Option<String>,
#[arg(long, value_parser = parse_image_ref)]
pub runtime: Option<ImageRef>,
#[arg(long, conflicts_with_all = ["vcpus", "memory"])]
pub size: Option<String>,
#[arg(long)]
pub vcpus: Option<u32>,
#[arg(long, value_parser = parse_size_to_mib)]
pub memory: Option<u64>,
#[arg(long, default_value_t = 30)]
pub timeout_seconds: u32,
#[arg(long)]
pub internet: bool,
#[arg(long)]
pub persistent: bool,
#[arg(long)]
pub updatable: bool,
#[arg(long)]
pub env_vars: Option<String>,
#[arg(long)]
pub persistent_volume: Option<Vec<String>>,
#[arg(long)]
pub ephemeral_volume: Option<Vec<String>>,
#[arg(long)]
pub immutable_volume: Option<Vec<String>>,
#[arg(long, value_enum, default_value_t = StorageEngineCli::Storage)]
pub storage_engine: StorageEngineCli,
#[arg(long, value_enum, default_value_t = PaymentTypeCli::Hold)]
pub payment_type: PaymentTypeCli,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct ProgramUpdateArgs {
pub item_hash: ItemHash,
pub path: PathBuf,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct ProgramDeleteArgs {
pub item_hash: ItemHash,
#[arg(long, default_value = "User deletion")]
pub reason: String,
#[arg(long)]
pub keep_code: bool,
#[arg(short = 'y', long)]
pub yes: bool,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct ProgramListArgs {
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct ProgramShowArgs {
pub item_hash: ItemHash,
}
#[cfg(test)]
mod credit_transfer_args_tests {
use super::*;
#[test]
fn parse_rfc3339_utc_accepts_z_suffix() {
let dt = parse_rfc3339_utc("2026-12-31T23:59:59Z").unwrap();
assert_eq!(dt.to_rfc3339(), "2026-12-31T23:59:59+00:00");
}
#[test]
fn parse_rfc3339_utc_accepts_offset_and_normalizes_to_utc() {
let dt = parse_rfc3339_utc("2026-12-31T23:59:59+01:00").unwrap();
assert_eq!(dt.to_rfc3339(), "2026-12-31T22:59:59+00:00");
}
#[test]
fn parse_rfc3339_utc_rejects_garbage() {
assert!(parse_rfc3339_utc("not a date").is_err());
}
fn assert_value_validation_err(args: &[&str]) {
match Cli::try_parse_from(args) {
Ok(_) => panic!("expected parse error for {args:?}"),
Err(e) => assert_eq!(e.kind(), clap::error::ErrorKind::ValueValidation),
}
}
#[test]
fn message_list_rejects_malformed_hashes_cleanly() {
assert_value_validation_err(&["aleph", "message", "list", "--hashes", "not-a-hash"]);
assert_value_validation_err(&[
"aleph",
"message",
"list",
"--content-hashes",
"definitely-not-hex",
]);
}
#[test]
fn post_list_rejects_malformed_hashes_cleanly() {
assert_value_validation_err(&["aleph", "post", "list", "--hashes", "not-a-hash"]);
}
#[test]
fn message_list_accepts_hex_addresses() {
let cli = Cli::try_parse_from([
"aleph",
"message",
"list",
"--addresses",
"0xABCD1234,0xEF560000",
"--owners",
"0xDEADBEEF",
])
.expect("clap parse");
match cli.command {
Commands::Message {
command: MessageCommand::List(args),
} => {
let addresses = args.filter.addresses.unwrap();
assert_eq!(addresses.len(), 2);
assert_eq!(addresses[0].to_string(), "0xABCD1234");
let owners = args.filter.owners.unwrap();
assert_eq!(owners.len(), 1);
assert_eq!(owners[0].to_string(), "0xDEADBEEF");
}
_ => panic!("expected message list"),
}
}
#[test]
fn post_list_accepts_hex_addresses() {
let cli =
Cli::try_parse_from(["aleph", "post", "list", "--addresses", "0xABCD1234"]).unwrap();
match cli.command {
Commands::Post {
command: PostCommand::List(args),
} => {
let addresses = args.filter.addresses.unwrap();
assert_eq!(addresses.len(), 1);
assert_eq!(addresses[0].to_string(), "0xABCD1234");
}
_ => panic!("expected post list"),
}
}
}
#[cfg(test)]
mod port_forward_args_tests {
use super::*;
#[test]
fn list_accepts_address_and_vm_id() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"list",
"--address",
"0xABCD1234",
"--vm-id",
"1111111111111111111111111111111111111111111111111111111111111111",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command:
InstanceCommand::PortForward {
command: PortForwardCommand::List(args),
},
} => {
assert_eq!(args.address.as_deref(), Some("0xABCD1234"));
assert!(args.vm_id.is_some());
}
_ => panic!("expected port-forward list"),
}
}
#[test]
fn create_rejects_port_zero() {
match Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"create",
"1111111111111111111111111111111111111111111111111111111111111111",
"0",
]) {
Ok(_) => panic!("expected parse error for port 0"),
Err(e) => assert_eq!(e.kind(), clap::error::ErrorKind::ValueValidation),
}
}
#[test]
fn create_rejects_port_above_max() {
let result = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"create",
"1111111111111111111111111111111111111111111111111111111111111111",
"65536",
]);
match result {
Ok(_) => panic!("expected parse error for port 65536"),
Err(e) => assert_eq!(e.kind(), clap::error::ErrorKind::ValueValidation),
}
}
#[test]
fn create_defaults_tcp_true_udp_false() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"create",
"1111111111111111111111111111111111111111111111111111111111111111",
"443",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command:
InstanceCommand::PortForward {
command: PortForwardCommand::Create(args),
},
} => {
assert_eq!(args.port, 443);
assert!(args.tcp);
assert!(!args.udp);
}
_ => panic!("expected port-forward create"),
}
}
#[test]
fn create_accepts_explicit_udp_only() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"create",
"1111111111111111111111111111111111111111111111111111111111111111",
"5353",
"--tcp",
"false",
"--udp",
"true",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command:
InstanceCommand::PortForward {
command: PortForwardCommand::Create(args),
},
} => {
assert!(!args.tcp);
assert!(args.udp);
}
_ => panic!("expected port-forward create"),
}
}
#[test]
fn delete_accepts_optional_port() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"delete",
"1111111111111111111111111111111111111111111111111111111111111111",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command:
InstanceCommand::PortForward {
command: PortForwardCommand::Delete(args),
},
} => {
assert!(args.port.is_none());
}
_ => panic!("expected port-forward delete"),
}
}
#[test]
fn create_accepts_hash_prefix() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"port-forward",
"create",
"a41fb91c3e68",
"443",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command:
InstanceCommand::PortForward {
command: PortForwardCommand::Create(args),
},
} => {
assert_eq!(args.vm_id, "a41fb91c3e68");
assert_eq!(args.port, 443);
}
_ => panic!("expected port-forward create"),
}
}
#[test]
fn pfw_alias_resolves_to_port_forward() {
let cli = Cli::try_parse_from(["aleph", "instance", "pfw", "list"]).expect("clap parse");
assert!(matches!(
cli.command,
Commands::Instance {
command: InstanceCommand::PortForward {
command: PortForwardCommand::List(_),
},
}
));
}
}
#[derive(Subcommand)]
pub enum DomainCommand {
List(DomainListArgs),
Add(DomainAddArgs),
Attach(DomainAttachArgs),
Detach(DomainDetachArgs),
Remove(DomainRemoveArgs),
}
#[derive(Args)]
pub struct DomainListArgs {
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct DomainAddArgs {
pub domain: String,
#[arg(long)]
pub target: String,
#[arg(long, value_enum, default_value = "ipfs")]
pub kind: DomainKindCli,
#[arg(long)]
pub catch_all_path: Option<String>,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct DomainAttachArgs {
pub domain: String,
#[arg(long = "to")]
pub target: String,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct DomainDetachArgs {
pub domain: String,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct DomainRemoveArgs {
pub domain: String,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
#[clap(rename_all = "lowercase")]
pub enum DomainKindCli {
Ipfs,
Program,
Instance,
}
#[derive(Subcommand)]
pub enum WebsiteCommand {
List(WebsiteListArgs),
Show(WebsiteShowArgs),
Deploy(WebsiteDeployArgs),
Update(WebsiteUpdateArgs),
Delete(WebsiteDeleteArgs),
}
#[derive(Args)]
pub struct WebsiteListArgs {
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct WebsiteShowArgs {
pub name: String,
#[arg(long)]
pub address: Option<String>,
}
#[derive(Args)]
pub struct WebsiteDeployArgs {
pub name: String,
pub path: std::path::PathBuf,
#[arg(long, value_enum, default_value = "none")]
pub framework: FrameworkCli,
#[arg(long)]
pub tag: Vec<String>,
#[arg(long)]
pub payment_chain: Option<String>,
#[arg(long, value_enum)]
pub payment_type: Option<PaymentTypeCli>,
#[arg(long)]
pub domain: Vec<String>,
#[arg(long)]
pub volume_id: Option<String>,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct WebsiteUpdateArgs {
pub name: String,
pub path: std::path::PathBuf,
#[arg(long, value_enum)]
pub framework: Option<FrameworkCli>,
#[arg(long)]
pub tag: Option<Vec<String>>,
#[arg(long)]
pub domain: Vec<String>,
#[arg(long)]
pub skip_domain_update: bool,
#[arg(long)]
pub volume_id: Option<String>,
#[arg(long)]
pub idempotent: bool,
#[arg(long)]
pub channel: Option<String>,
#[arg(long, value_enum)]
pub payment_type: Option<PaymentTypeCli>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct WebsiteDeleteArgs {
pub name: String,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub channel: Option<String>,
#[arg(long)]
pub on_behalf_of: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[cfg(test)]
mod instance_delete_args_tests {
use super::*;
const HASH: &str = "a41fb91c3e68370759b72338dd1947f18e2ed883837aec5dc731d5f427f90564";
#[test]
fn parses_minimal_invocation() {
let cli = Cli::try_parse_from(["aleph", "instance", "delete", HASH]).expect("clap parse");
match cli.command {
Commands::Instance {
command: InstanceCommand::Delete(args),
} => {
assert_eq!(args.vm_id.to_string(), HASH);
assert_eq!(args.reason, "User deletion");
assert!(!args.yes);
assert!(!args.signing.dry_run);
}
_ => panic!("expected instance delete"),
}
}
#[test]
fn accepts_reason_and_yes() {
let cli = Cli::try_parse_from([
"aleph",
"instance",
"delete",
HASH,
"--reason",
"decommission",
"-y",
])
.expect("clap parse");
match cli.command {
Commands::Instance {
command: InstanceCommand::Delete(args),
} => {
assert_eq!(args.reason, "decommission");
assert!(args.yes);
}
_ => panic!("expected instance delete"),
}
}
#[test]
fn rejects_missing_vm_id() {
let result = Cli::try_parse_from(["aleph", "instance", "delete"]);
assert!(result.is_err());
}
#[test]
fn accepts_hash_prefix() {
let cli = Cli::try_parse_from(["aleph", "instance", "delete", "a41fb91c3e68"])
.expect("clap parse");
match cli.command {
Commands::Instance {
command: InstanceCommand::Delete(args),
} => {
assert_eq!(args.vm_id, "a41fb91c3e68");
}
_ => panic!("expected instance delete"),
}
}
#[test]
fn accepts_dry_run_flag() {
let cli = Cli::try_parse_from(["aleph", "instance", "delete", HASH, "--dry-run"])
.expect("clap parse");
match cli.command {
Commands::Instance {
command: InstanceCommand::Delete(args),
} => {
assert!(args.signing.dry_run);
}
_ => panic!("expected instance delete"),
}
}
}
#[derive(Subcommand)]
pub enum PortForwardCommand {
List(PortForwardListArgs),
Create(PortForwardCreateArgs),
Update(PortForwardUpdateArgs),
Delete(PortForwardDeleteArgs),
Refresh(PortForwardRefreshArgs),
}
#[derive(Args)]
pub struct PortForwardListArgs {
#[arg(long)]
pub address: Option<String>,
#[arg(long)]
pub vm_id: Option<String>,
}
#[derive(Args)]
pub struct PortForwardCreateArgs {
pub vm_id: String,
#[arg(value_parser = clap::value_parser!(u16).range(1..=65535))]
pub port: u16,
#[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
pub tcp: bool,
#[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
pub udp: bool,
#[arg(long)]
pub channel: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct PortForwardUpdateArgs {
pub vm_id: String,
#[arg(value_parser = clap::value_parser!(u16).range(1..=65535))]
pub port: u16,
#[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
pub tcp: bool,
#[arg(long, default_value_t = false, action = clap::ArgAction::Set)]
pub udp: bool,
#[arg(long)]
pub channel: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct PortForwardDeleteArgs {
pub vm_id: String,
#[arg(long, value_parser = clap::value_parser!(u16).range(1..=65535))]
pub port: Option<u16>,
#[arg(short = 'y', long)]
pub yes: bool,
#[arg(long)]
pub channel: Option<String>,
#[command(flatten)]
pub signing: SigningArgs,
}
#[derive(Args)]
pub struct PortForwardRefreshArgs {
pub vm_id: String,
#[command(flatten)]
pub identity: IdentityArgs,
}
#[derive(Subcommand)]
pub enum ConfidentialCommand {
InitSession(ConfidentialInitSessionArgs),
Start(ConfidentialStartArgs),
#[command(alias = "create")]
Launch(ConfidentialLaunchArgs),
}
#[derive(Args)]
pub struct ConfidentialInitSessionArgs {
pub vm_id: String,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
#[arg(long, default_value_t = 0x1)]
pub policy: u32,
#[arg(long)]
pub keep_session: bool,
#[arg(long)]
pub debug: bool,
}
#[derive(Args)]
pub struct ConfidentialStartArgs {
pub vm_id: String,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
#[arg(long)]
pub firmware_hash: Option<String>,
#[arg(long)]
pub firmware_file: Option<std::path::PathBuf>,
#[arg(long)]
pub secret: Option<String>,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub debug: bool,
}
#[derive(Args)]
pub struct ConfidentialLaunchArgs {
pub vm_id: Option<String>,
#[arg(long, alias = "crn-url")]
pub crn: Option<String>,
#[command(flatten)]
pub identity: IdentityArgs,
#[arg(long, default_value_t = 0x1)]
pub policy: u32,
#[arg(long)]
pub keep_session: bool,
#[arg(long)]
pub firmware_hash: Option<String>,
#[arg(long)]
pub firmware_file: Option<std::path::PathBuf>,
#[arg(long)]
pub secret: Option<String>,
#[arg(long)]
pub debug: bool,
}
#[cfg(test)]
mod confidential_parser_tests {
use super::*;
use clap::Parser;
fn parse(args: &[&str]) -> Cli {
Cli::try_parse_from(args).expect("parse")
}
#[test]
fn init_session_accepts_hash_prefix() {
let cli = parse(&[
"aleph",
"instance",
"confidential",
"init-session",
"236328f6",
]);
let Commands::Instance {
command: InstanceCommand::Confidential(ConfidentialCommand::InitSession(a)),
} = cli.command
else {
panic!("wrong subcommand");
};
assert_eq!(a.vm_id, "236328f6");
assert_eq!(a.policy, 0x1);
assert!(!a.keep_session);
}
#[test]
fn start_accepts_secret_and_json() {
let cli = parse(&[
"aleph",
"instance",
"confidential",
"start",
"236328f6",
"--secret",
"hunter2",
"--json",
]);
let Commands::Instance {
command: InstanceCommand::Confidential(ConfidentialCommand::Start(a)),
} = cli.command
else {
panic!("wrong subcommand");
};
assert_eq!(a.secret.as_deref(), Some("hunter2"));
assert!(a.json);
}
#[test]
fn launch_accepts_no_positional() {
let cli = parse(&["aleph", "instance", "confidential", "launch"]);
let Commands::Instance {
command: InstanceCommand::Confidential(ConfidentialCommand::Launch(a)),
} = cli.command
else {
panic!("wrong subcommand");
};
assert_eq!(a.vm_id, None);
}
#[test]
fn launch_accepts_create_alias() {
let cli = parse(&["aleph", "instance", "confidential", "create"]);
let Commands::Instance {
command: InstanceCommand::Confidential(ConfidentialCommand::Launch(_)),
} = cli.command
else {
panic!("wrong subcommand");
};
}
}