diff --git a/src/cli.rs b/src/cli.rs index ca0ab2c..88ca51e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -47,9 +47,17 @@ impl FolderRemoval { } #[derive(Debug, Parser)] -#[command(name = "appa", about = "Local-first peer-to-peer folder sync")] +#[command( + name = "appa", + about = "Local-first peer-to-peer folder sync", + after_help = "Run `appa --help` for command-specific usage. Most commands connect to the local daemon; start it with `appa run` or `appa service install`." +)] struct CommandLine { - #[arg(long, global = true)] + #[arg( + long, + global = true, + help = "Required for identity and config commands after stopping the daemon; ignored by other commands" + )] offline: bool, #[command(subcommand)] command: Command, @@ -57,137 +65,209 @@ struct CommandLine { #[derive(Debug, Subcommand)] enum Command { + #[command(about = "Run the local synchronization daemon directly")] Daemon { #[command(subcommand)] command: DaemonCommand, }, + #[command(about = "Add an existing directory as a new synced folder")] Init { + #[arg(value_name = "FOLDER", help = "Existing directory to synchronize")] folder: String, }, + #[command(about = "Show, export, or import this device's identity")] Identity { #[command(subcommand)] command: Option, }, + #[command(about = "Create a reusable invitation for a synced folder")] Invite { + #[arg(value_name = "FOLDER", help = "Folder to invite another device to")] folder: String, - #[arg(long)] + #[arg(long, help = "Copy the invitation to the system clipboard")] copy: bool, - #[arg(long)] + #[arg(long, help = "Print the invitation as a terminal QR code")] qr: bool, - #[arg(long)] + #[arg(long, value_name = "PATH", help = "Write the invitation to a file")] file: Option, }, + #[command(about = "Join a folder with an invitation")] Join { + #[arg( + value_name = "FOLDER", + help = "Existing directory to join to the invitation" + )] folder: String, + #[arg( + value_name = "TICKET", + help = "Invitation text; omit when using --stdin" + )] ticket: Option, - #[arg(long)] + #[arg(long, help = "Read the invitation from standard input")] stdin: bool, - #[arg(long)] + #[arg(long, help = "Skip the confirmation prompt")] yes: bool, }, + #[command(about = "Remove a folder's local Appa state without changing its files")] Forget { + #[arg(value_name = "FOLDER", help = "Folder to forget")] folder: String, - #[arg(long)] + #[arg(long, help = "Skip the confirmation prompt")] yes: bool, }, + #[command(about = "Stop syncing a local folder without changing its files")] Leave { + #[arg(value_name = "FOLDER", help = "Folder to leave")] folder: String, - #[arg(long)] + #[arg(long, help = "Skip the confirmation prompt")] yes: bool, }, + #[command(about = "Run the daemon and synchronize folders")] Run { + #[arg( + value_name = "FOLDER", + help = "Synchronize only this folder; defaults to all folders" + )] folder: Option, }, + #[command(about = "Show synchronization health")] Status { + #[arg( + value_name = "FOLDER", + help = "Show one folder; defaults to all folders" + )] folder: Option, - #[arg(long)] + #[arg(long, help = "Emit machine-readable JSON")] json: bool, - #[arg(long, conflicts_with = "json")] + #[arg( + long, + conflicts_with = "json", + help = "Refresh the human-readable view every second" + )] watch: bool, }, + #[command(about = "List peer routes for a folder")] Peers { + #[arg(value_name = "FOLDER", help = "Folder whose peers to list")] folder: String, - #[arg(long)] + #[arg(long, help = "Emit machine-readable JSON")] json: bool, }, + #[command(about = "List saved revisions for a folder")] History { + #[arg(value_name = "FOLDER", help = "Folder whose history to show")] folder: String, }, + #[command(about = "Restore a saved folder revision")] Restore { + #[arg(value_name = "FOLDER", help = "Folder to restore")] folder: String, - #[arg(allow_negative_numbers = true)] + #[arg( + allow_negative_numbers = true, + value_name = "REVISION", + help = "Revision to restore; negative numbers are allowed" + )] revision: i64, }, + #[command(about = "List conflict copies in a folder")] Conflicts { + #[arg(value_name = "FOLDER", help = "Folder whose conflicts to list")] folder: String, }, + #[command(about = "List devices that can access a folder")] Members { + #[arg(value_name = "FOLDER", help = "Folder whose members to list")] folder: String, }, + #[command(about = "Remove a device from a folder and rotate its capability")] Revoke { + #[arg(value_name = "FOLDER", help = "Folder to update")] folder: String, + #[arg(value_name = "DEVICE_ID", help = "Device identity to revoke")] device_id: String, }, + #[command(about = "Generate shell-completion code")] Completions { + #[arg( + value_name = "SHELL", + help = "Shell: bash, elvish, fish, powershell, or zsh" + )] shell: Shell, }, + #[command(about = "Manage the background Appa user service")] Service { #[command(subcommand)] command: ServiceCommand, }, + #[command(about = "Validate and apply declarative folder configuration")] Config { #[command(subcommand)] command: ConfigCommand, }, + #[command(about = "Check Appa state for common problems")] Doctor { - #[arg(long)] + #[arg(long, help = "Emit machine-readable JSON")] json: bool, }, } #[derive(Debug, Subcommand)] enum ConfigCommand { + #[command(about = "Write a configuration template; fails if the file exists")] Init { - #[arg(default_value = DEFAULT_CONFIG_PATH)] + #[arg(default_value = DEFAULT_CONFIG_PATH, value_name = "PATH", help = "Configuration path")] path: PathBuf, }, + #[command(about = "Validate a configuration file and its referenced folders")] Check { - #[arg(default_value = DEFAULT_CONFIG_PATH)] + #[arg(default_value = DEFAULT_CONFIG_PATH, value_name = "PATH", help = "Configuration path")] path: PathBuf, }, + #[command(about = "Register configured folders and update their modes")] Apply { - #[arg(default_value = DEFAULT_CONFIG_PATH)] + #[arg(default_value = DEFAULT_CONFIG_PATH, value_name = "PATH", help = "Configuration path")] path: PathBuf, }, + #[command(about = "Preview configuration changes without modifying state")] Audit { - #[arg(default_value = DEFAULT_CONFIG_PATH)] + #[arg(default_value = DEFAULT_CONFIG_PATH, value_name = "PATH", help = "Configuration path")] path: PathBuf, }, } #[derive(Debug, Subcommand)] enum IdentityCommand { + #[command(about = "Write a sensitive backup of this device's identity")] Export { + #[arg(value_name = "PATH", help = "Destination backup path")] path: PathBuf, }, + #[command(about = "Replace this device's identity from a backup")] Import { + #[arg(value_name = "PATH", help = "Identity backup to import")] path: PathBuf, - #[arg(long)] + #[arg(long, help = "Skip the replacement confirmation")] yes: bool, }, } #[derive(Debug, Subcommand)] enum ServiceCommand { + #[command(about = "Install and start the Appa user service")] Install, + #[command(about = "Show the Appa user service status")] Status, + #[command(about = "Restart the Appa user service")] Restart, + #[command(about = "Show recent Appa user service logs")] Logs, + #[command(about = "Stop and remove the Appa user service")] Uninstall, } #[derive(Debug, Subcommand)] enum DaemonCommand { + #[command(about = "Serve the local Appa daemon")] Serve, }