use clap::{Parser, Subcommand, ValueEnum};
use git_remote_htree::nostr_client::PullRequestStateFilter;
use std::path::PathBuf;
const CLI_HELP_TEMPLATE: &str = "\
{about-with-newline}\
\n{usage-heading} {usage}\n\n\
Options:\n\
{options}\
{after-help}";
#[cfg(feature = "fuse")]
const CLI_GROUPED_COMMANDS: &str = "\
\nDaemon Commands:
start Start the hashtree daemon
reload Reload daemon config by restarting with saved launch args
stop Stop the hashtree daemon
status Show daemon status (peers, storage, etc.)
peer Show connected P2P peers
Content Commands:
add Add file or directory to hashtree (like ipfs add)
pwa Capture an installable web app into hashtree
load Load/prefetch content into local hashtree storage
get Get/download content by CID
cat Output file content to stdout (like cat)
push Push content to file servers (Blossom)
info Get information about a CID
Storage Commands:
pin Pin content
unpin Unpin content
pins List all pinned content
stats Get storage statistics
gc Run garbage collection
storage Manage storage limits and eviction
mount Mount a hashtree via FUSE
mounts List active hashtree mounts
Publishing & Git Commands:
publish Publish a hash to Nostr under a ref name
release Manage published release trees
repos List published git repositories for yourself or another user
pr Pull request management
Identity & Social Commands:
user Show or set your nostr identity
profile Show or update your Nostr profile
mirror Manage mirrored authors
follow Follow a user (adds to your contact list)
unfollow Unfollow a user (removes from your contact list)
following List users you follow
mute Mute a user (adds to your mute list)
unmute Unmute a user (removes from your mute list)
muted List users you mute
socialgraph Social graph utilities
Wallet Commands:
cashu Manage Cashu wallet and accepted mints
General Commands:
help Print this message or the help of the given subcommand(s)";
#[cfg(not(feature = "fuse"))]
const CLI_GROUPED_COMMANDS: &str = "\
\nDaemon Commands:
start Start the hashtree daemon
reload Reload daemon config by restarting with saved launch args
stop Stop the hashtree daemon
status Show daemon status (peers, storage, etc.)
peer Show connected P2P peers
Content Commands:
add Add file or directory to hashtree (like ipfs add)
pwa Capture an installable web app into hashtree
load Load/prefetch content into local hashtree storage
get Get/download content by CID
cat Output file content to stdout (like cat)
push Push content to file servers (Blossom)
info Get information about a CID
Storage Commands:
pin Pin content
unpin Unpin content
pins List all pinned content
stats Get storage statistics
gc Run garbage collection
storage Manage storage limits and eviction
mounts List active hashtree mounts
Publishing & Git Commands:
publish Publish a hash to Nostr under a ref name
release Manage published release trees
repos List published git repositories for yourself or another user
pr Pull request management
Identity & Social Commands:
user Show or set your nostr identity
profile Show or update your Nostr profile
mirror Manage mirrored authors
follow Follow a user (adds to your contact list)
unfollow Unfollow a user (removes from your contact list)
following List users you follow
mute Mute a user (adds to your mute list)
unmute Unmute a user (removes from your mute list)
muted List users you mute
socialgraph Social graph utilities
Wallet Commands:
cashu Manage Cashu wallet and accepted mints
General Commands:
help Print this message or the help of the given subcommand(s)";
#[derive(Parser)]
#[command(name = "htree")]
#[command(version)]
#[command(about = "Content-addressed filesystem", long_about = None)]
#[command(help_template = CLI_HELP_TEMPLATE)]
#[command(after_help = CLI_GROUPED_COMMANDS)]
pub(crate) struct Cli {
#[arg(long, global = true, env = "HTREE_DATA_DIR")]
pub(crate) data_dir: Option<PathBuf>,
#[command(subcommand)]
pub(crate) command: Commands,
}
impl Cli {
pub(crate) fn data_dir(&self) -> PathBuf {
self.data_dir
.clone()
.unwrap_or_else(|| hashtree_cli::config::get_hashtree_dir().join("data"))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum StartMode {
Normal,
#[value(alias = "signal-only")]
Assist,
}
impl From<StartMode> for hashtree_cli::config::ServerMode {
fn from(value: StartMode) -> Self {
match value {
StartMode::Normal => Self::Normal,
StartMode::Assist => Self::Assist,
}
}
}
#[derive(Subcommand)]
pub(crate) enum Commands {
Start {
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
#[arg(long)]
relays: Option<String>,
#[arg(long, value_enum)]
mode: Option<StartMode>,
#[arg(long)]
daemon: bool,
#[arg(long, requires = "daemon")]
log_file: Option<PathBuf>,
#[arg(long, requires = "daemon")]
pid_file: Option<PathBuf>,
},
Stop {
#[arg(long)]
pid_file: Option<PathBuf>,
},
Reload {
#[arg(long)]
pid_file: Option<PathBuf>,
},
Status {
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
},
Peer {
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
},
Add {
path: PathBuf,
#[arg(long)]
only_hash: bool,
#[arg(long = "unencrypted", alias = "public")]
unencrypted: bool,
#[arg(long)]
no_ignore: bool,
#[arg(long)]
publish: Option<String>,
#[arg(long)]
chunk_size: Option<usize>,
#[arg(long)]
local: bool,
},
Pwa {
#[command(subcommand)]
command: PwaCommands,
},
Get {
cid: String,
#[arg(short, long)]
output: Option<PathBuf>,
},
Load {
cid: String,
},
Cat {
cid: String,
},
Push {
cid: String,
#[arg(long, short)]
server: Option<String>,
},
Info {
cid: String,
},
Pin {
cid: String,
},
Unpin {
cid: String,
},
Pins,
Stats,
Gc,
Storage {
#[command(subcommand)]
command: StorageCommands,
},
#[cfg(feature = "fuse")]
Mount {
target: String,
mountpoint: Option<PathBuf>,
#[arg(long)]
visibility: Option<String>,
#[arg(long)]
link_key: Option<String>,
#[arg(long)]
private: bool,
#[arg(long)]
relays: Option<String>,
#[arg(long)]
allow_other: bool,
},
Mounts {
#[arg(long)]
json: bool,
},
Publish {
ref_name: String,
hash: String,
#[arg(long)]
key: Option<String>,
},
Release {
#[command(subcommand)]
command: ReleaseCommands,
},
Repos {
owner: Option<String>,
},
Pr {
#[command(subcommand)]
command: PrCommands,
},
User {
identity: Option<String>,
},
Profile {
#[arg(long)]
name: Option<String>,
#[arg(long)]
about: Option<String>,
#[arg(long)]
picture: Option<String>,
},
Mirror {
#[command(subcommand)]
command: MirrorCommands,
},
Follow {
npub: String,
},
Unfollow {
npub: String,
},
Following,
Mute {
npub: String,
#[arg(long)]
reason: Option<String>,
},
Unmute {
npub: String,
},
Muted,
Socialgraph {
#[command(subcommand)]
command: SocialGraphCommands,
},
Cashu {
#[command(subcommand)]
command: CashuCommands,
},
}
#[derive(Subcommand)]
pub(crate) enum PrCommands {
Create {
repo: Option<String>,
#[arg(long, short)]
title: String,
#[arg(long, short)]
description: Option<String>,
#[arg(long)]
branch: Option<String>,
#[arg(long, default_value = "master")]
target_branch: String,
#[arg(long)]
clone_url: Option<String>,
},
List {
repo: Option<String>,
#[arg(long, value_enum, default_value_t = PrListState::Open)]
state: PrListState,
},
}
#[derive(Subcommand)]
pub(crate) enum MirrorCommands {
Add {
npub: String,
},
#[command(name = "rm", alias = "remove")]
Rm {
npub: String,
},
#[command(name = "ls", alias = "list")]
Ls,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum PrListState {
Open,
Applied,
Closed,
Draft,
All,
}
impl PrListState {
pub(crate) fn to_filter(self) -> PullRequestStateFilter {
match self {
Self::Open => PullRequestStateFilter::Open,
Self::Applied => PullRequestStateFilter::Applied,
Self::Closed => PullRequestStateFilter::Closed,
Self::Draft => PullRequestStateFilter::Draft,
Self::All => PullRequestStateFilter::All,
}
}
}
#[derive(Subcommand)]
pub(crate) enum StorageCommands {
Stats,
Trees,
Evict,
Compact {
#[arg(long = "env-dir")]
env_dirs: Vec<PathBuf>,
#[arg(long)]
keep_backup: bool,
},
TrimLmdb {
#[arg(long = "env-dir")]
env_dir: PathBuf,
#[arg(long = "max-gb")]
max_gb: u64,
},
Verify {
#[arg(long)]
delete: bool,
#[arg(long)]
r2: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum CashuCommands {
#[command(visible_alias = "status")]
Balance {
#[arg(long)]
mint: Option<String>,
},
#[command(visible_alias = "load")]
Topup {
amount_sat: u64,
#[arg(long)]
mint: Option<String>,
},
Mint {
#[command(subcommand)]
command: CashuMintCommands,
},
}
#[derive(Subcommand)]
pub(crate) enum PwaCommands {
Export {
url: String,
#[arg(long)]
json: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum CashuMintCommands {
List,
Add {
url: String,
#[arg(long = "default")]
make_default: bool,
},
Remove {
url: String,
},
Default {
url: String,
},
}
#[derive(Subcommand)]
pub(crate) enum SocialGraphCommands {
Filter {
#[arg(long)]
max_distance: Option<u32>,
#[arg(long, default_value_t = 1.0)]
overmute_threshold: f64,
},
Stats,
Warm {
#[arg(long, default_value_t = 60)]
secs: u64,
#[arg(long)]
crawl_depth: Option<u32>,
#[arg(long, default_value_t = false)]
full_graph_recrawl: bool,
#[arg(long = "relay")]
relays: Vec<String>,
#[arg(long, default_value_t = 64)]
author_batch_size: usize,
#[arg(long, default_value_t = 4)]
concurrent_batches: usize,
},
Snapshot {
#[arg(long, short)]
out: PathBuf,
#[arg(long)]
max_nodes: Option<usize>,
#[arg(long)]
max_edges: Option<usize>,
#[arg(long)]
max_distance: Option<u32>,
#[arg(long)]
max_edges_per_node: Option<usize>,
},
RebuildProfileIndex,
RebuildEventIndex,
Index {
#[arg(long, default_value_t = 0)]
warm_secs: u64,
#[arg(long)]
crawl_depth: Option<u32>,
#[arg(long, default_value_t = false)]
full_graph_recrawl: bool,
#[arg(long)]
max_follow_distance: Option<u32>,
#[arg(long, default_value_t = 64)]
max_authors: usize,
#[arg(long, default_value_t = 256)]
max_live_mb: u64,
#[arg(long, default_value_t = 256)]
per_author_event_limit: usize,
#[arg(long)]
per_author_live_bytes: Option<u64>,
#[arg(long, default_value_t = 64)]
author_batch_size: usize,
#[arg(long, default_value_t = 4)]
concurrent_batches: usize,
#[arg(long, default_value_t = 10)]
fetch_timeout_secs: u64,
#[arg(long)]
relay_event_max_bytes: Option<u32>,
#[arg(long, default_value_t = false)]
global_relay_scan: bool,
#[arg(long, default_value_t = false)]
full_author_history: bool,
#[arg(long)]
author_allowlist_url: Option<String>,
#[arg(long, default_value_t = false)]
negentropy_only: bool,
#[arg(long, default_value_t = 1_000)]
relay_page_size: usize,
#[arg(long, default_value_t = 10)]
max_relay_pages: usize,
#[arg(long)]
max_events_seen: Option<usize>,
#[arg(long = "kind")]
kinds: Vec<u16>,
#[arg(long = "relay")]
relays: Vec<String>,
},
}
#[derive(Subcommand)]
pub(crate) enum ReleaseCommands {
Publish {
tree_name: String,
version_path: String,
cid: String,
#[arg(long)]
local: bool,
},
}