Module: nodalync-cli
Source: Not in spec (application layer)
Overview
Command-line interface for interacting with a Nodalync node. User-facing binary.
Dependencies
- All
nodalync-*crates clap— Argument parsingindicatif— Progress barscolored— Terminal colors
Commands
Identity
# Initialize new identity
nodalync init
> Identity created: ndl1abc123...
> Configuration saved to <data_dir>/config.toml
# Show identity
nodalync whoami
> PeerId: ndl1abc123...
> Public Key: 0x...
> Addresses: /ip4/0.0.0.0/tcp/9000
Content Management
# Publish content
nodalync publish <file> [--price <amount>] [--visibility <private|unlisted|shared>]
> Hashing content...
> Extracting L1 mentions... (23 found)
> Published: a1b2c3d4e5f6...
> Price: 0.10 HBAR
> Visibility: shared
# List local content
nodalync list [--visibility <filter>]
> SHARED (3)
> a1b2c3d4e5f6... "Research Paper" v3, 0.10 HBAR, 847 queries
> b7c8d9e0f1a2... "Analysis" v1, 0.05 HBAR, 234 queries
>
> PRIVATE (2)
> d9e0f1a2b3c4... "Draft Ideas" v4
> e5f6a7b8c9d0... "Personal Notes" v1
# Update content (new version)
nodalync update <hash> <new-file>
> Previous: a1b2c3d4e5f6... (v1)
> New: b7c8d9e0f1a2... (v2)
> Version root: a1b2c3d4e5f6...
# Show versions
nodalync versions <hash>
> Version root: a1b2c3d4e5f6...
> v1: a1b2c3d4e5f6... (2025-01-15) - shared
> v2: b7c8d9e0f1a2... (2025-01-20) - shared [latest]
# Change visibility
nodalync visibility <hash> --level <private|unlisted|shared>
> Visibility updated: a1b2c3d4e5f6... → shared
# Delete (local only)
nodalync delete <hash>
> Deleted: a1b2c3d4e5f6... (local copy only, provenance preserved)
Discovery & Querying
# Search network
nodalync search "climate change mitigation" [--limit <n>]
> Found 47 results
> [1] b7c8d9e0f1a2... "IPCC Report Summary" by ndl1def... (0.05/query, 847 queries)
> Preview: Global temperatures have risen 1.1°C since pre-industrial...
> [2] c3d4e5f6a7b8... "Carbon Capture Analysis" by ndl1ghi... (0.12/query, 234 queries)
> Preview: Current carbon capture technology can sequester...
# Preview content (free)
nodalync preview <hash>
> Title: "IPCC Report Summary"
> Owner: ndl1def...
> Price: 0.05 HBAR
> Queries: 847
>
> L1 Mentions (5 of 23):
> - Global temperatures have risen 1.1°C since pre-industrial
> - Net-zero by 2050 requires 45% emission reduction by 2030
> - ...
# Query content (paid)
nodalync query <hash>
> Querying b7c8d9e0f1a2...
> Payment: 0.05 HBAR
> Content saved to ./cache/b7c8d9e0f1a2...
Synthesis
# Create L3 insight from sources
nodalync synthesize --sources <hash1>,<hash2>,... --output <file>
> Verifying sources queried... ✓
> Computing provenance (12 roots)...
> L3 hash: f1a2b3c4d5e6...
>
> Publish now? [y/n/set price]: 0.15
> Published: f1a2b3c4d5e6... (0.15 HBAR, shared)
# Reference external L3 as L0
nodalync reference <l3-hash>
> Referencing a1b2c3d4e5f6... as L0 for future derivations
Economics
# Check balance
nodalync balance
> Protocol Balance: 127.50 HBAR
> Pending Earnings: 4.23 HBAR
> Pending Settlement: 12 payments
>
> Breakdown:
> Direct queries: 89.20 HBAR
> Root contributions: 38.30 HBAR
# Earnings by content
nodalync earnings [--content <hash>]
> Top earning content:
> a1b2c3d4e5f6... "Research Paper": 45.30 HBAR (234 queries)
> b7c8d9e0f1a2... "Analysis": 23.10 HBAR (462 queries, as root)
# Deposit tokens
nodalync deposit <amount>
> Depositing 50.00 HBAR...
> Transaction: 0x...
> New balance: 177.50 HBAR
# Withdraw tokens
nodalync withdraw <amount>
> Withdrawing 100.00 HBAR...
> Transaction: 0x...
> New balance: 77.50 HBAR
# Force settlement
nodalync settle
> Settling 12 pending payments...
> Batch ID: 0a1b2c3d4e5f...
> Transaction: 0x...
> Settled: 4.23 HBAR to 5 recipients
Payment Channels
# Open payment channel with peer
nodalync open-channel <peer-id> --deposit 100
> Channel opened: 4d5e6f7a8b9c...
> Peer: ndl1abc123...
> State: Open
> My Balance: 100.00 HBAR
> Their Balance: 100.00 HBAR
# List all payment channels
nodalync list-channels
> Payment Channels: 3 channels (2 open)
> 1a2b3c4d5e6f... ndl1abc... [Open] my: 0.85 HBAR / their: 1.15 HBAR
> 2b3c4d5e6f7a... ndl1def... [Open] my: 2.30 HBAR / their: 0.70 HBAR (5 pending)
> 3c4d5e6f7a8b... ndl1ghi... [Closed] my: 0.00 HBAR / their: 0.00 HBAR
# Close payment channel
nodalync close-channel <peer-id>
> Channel closed: 4d5e6f7a8b9c...
> Peer: ndl1abc123...
> Final Balance: my: 0.85 HBAR / their: 1.15 HBAR
Node Management
# Start node (foreground)
nodalync start
> Starting Nodalync node...
> PeerId: 12D3KooW...
> Listening on /ip4/0.0.0.0/tcp/9000
> Connected to 12 peers
> DHT bootstrapped
# Start with health endpoint (for containers/monitoring)
nodalync start --health --health-port 8080
> Starting Nodalync node...
> PeerId: 12D3KooW...
> Health endpoint: http://0.0.0.0:8080/health
> Metrics endpoint: http://0.0.0.0:8080/metrics
# Start as daemon (background)
nodalync start --daemon
> Nodalync daemon started (PID: 12345)
> PeerId: 12D3KooW...
# Node status
nodalync status
> Node: running (PID: 12345)
> PeerId: 12D3KooW...
> Uptime: 4h 23m
> Peers: 12 connected
> Content: 5 shared, 2 private
> Pending: 12 payments (4.23 HBAR)
# Stop daemon
nodalync stop
> Shutting down gracefully...
> Flushing pending operations...
> Node stopped
Health Endpoints (when --health flag is used):
| Endpoint | Content-Type | Description |
|---|---|---|
GET /health | application/json | {"status":"ok","connected_peers":N,"uptime_secs":M} |
GET /metrics | text/plain | Prometheus metrics format |
Prometheus Metrics:
nodalync_connected_peers— Current peer countnodalync_peer_events_total{event}— Connect/disconnect eventsnodalync_dht_operations_total{op,result}— DHT put/get operationsnodalync_gossipsub_messages_total— Broadcast messages receivednodalync_settlement_batches_total{status}— Settlement batchesnodalync_settlement_latency_seconds— Settlement operation latencynodalync_queries_total— Total queries processednodalync_query_latency_seconds— Query latency histogramnodalync_uptime_seconds— Node uptimenodalync_node_info{version,peer_id}— Node metadata
CLI Structure
#![allow(unused)]
fn main() {
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "nodalync")]
#[command(about = "Nodalync Protocol CLI")]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// Path to config file (default: <data_dir>/config.toml)
#[arg(short, long)]
pub config: Option<PathBuf>,
/// Output format
#[arg(short, long, default_value = "human")]
pub format: OutputFormat,
}
#[derive(Subcommand)]
pub enum Commands {
/// Initialize new identity
Init,
/// Show identity info
Whoami,
/// Publish content
Publish {
file: PathBuf,
#[arg(short, long)]
price: Option<f64>,
#[arg(short, long, default_value = "shared")]
visibility: Visibility,
},
/// List local content
List {
#[arg(short, long)]
visibility: Option<Visibility>,
},
/// Search network
Search {
query: String,
#[arg(short, long, default_value = "10")]
limit: u32,
},
/// Preview content (free)
Preview { hash: String },
/// Query content (paid)
Query { hash: String },
/// Create L3 synthesis
Synthesize {
#[arg(short, long, value_delimiter = ',')]
sources: Vec<String>,
#[arg(short, long)]
output: PathBuf,
},
/// Check balance
Balance,
/// Start node
Start {
#[arg(short, long)]
daemon: bool,
/// Enable HTTP health endpoint
#[arg(long)]
health: bool,
/// Port for health endpoint (default: 8080)
#[arg(long, default_value = "8080")]
health_port: u16,
},
/// Node status
Status,
/// Stop node
Stop,
/// Open payment channel
OpenChannel {
peer_id: String,
#[arg(short, long)]
deposit: f64,
},
/// Close payment channel
CloseChannel { peer_id: String },
/// List payment channels
ListChannels,
// ... more commands
}
#[derive(Clone, Copy, ValueEnum)]
pub enum OutputFormat {
Human,
Json,
}
}
Output Formatting
#![allow(unused)]
fn main() {
pub trait Render {
fn render_human(&self) -> String;
fn render_json(&self) -> String;
}
impl Render for SearchResult {
fn render_human(&self) -> String {
format!(
"{} \"{}\" by {} ({}/query, {} queries)\n Preview: {}",
self.hash.short(),
self.title,
self.owner.short(),
format_amount(self.price),
self.total_queries,
self.l1_summary.summary.truncate(80),
)
}
fn render_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap()
}
}
}
Error Handling
pub fn run() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Publish { file, price, visibility } => {
let result = publish(&file, price, visibility)?;
println!("{}", result.render(cli.format));
}
// ...
}
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("{}: {}", "Error".red().bold(), e);
std::process::exit(1);
}
}
Configuration
Configuration is stored in a platform-specific data directory (set NODALYNC_DATA_DIR to override):
- macOS:
~/Library/Application Support/io.nodalync.nodalync/config.toml - Linux:
~/.local/share/nodalync/config.toml - Windows:
%APPDATA%\nodalync\nodalync\config.toml
[identity]
keyfile = "<data_dir>/identity/keypair.key"
[storage]
content_dir = "<data_dir>/content"
database = "<data_dir>/nodalync.db"
cache_dir = "<data_dir>/cache"
cache_max_size_mb = 1000
[network]
enabled = true
listen_addresses = ["/ip4/0.0.0.0/tcp/9000"]
bootstrap_nodes = [
"/dns4/nodalync-bootstrap.eastus.azurecontainer.io/tcp/9000/p2p/12D3KooWMqrUmZm4e1BJTRMWqKHCe1TSX9Vu83uJLEyCGr2dUjYm",
]
[settlement]
network = "hedera-testnet"
auto_deposit = false
[economics]
default_price = 0.1 # In HBAR
auto_settle_threshold = 100.0 # In HBAR
[display]
default_format = "human"
show_previews = true
max_search_results = 20
Test Cases
- init: Creates identity and config
- publish: File hashed, L1 extracted, announced
- search: Returns results from network
- query: Pays and retrieves content
- synthesize: Creates L3 with correct provenance
- balance: Shows correct amounts
- JSON output: Valid JSON for all commands
- Error messages: Helpful, actionable errors
- open-channel: Opens channel, both sides have state
- list-channels: Shows all channels with states
- close-channel: Cooperative close, settles on-chain