Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
507 changes: 163 additions & 344 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions crates/akroasis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@ path = "src/main.rs"

[dependencies]
clap = { workspace = true }
comfy-table = "7"
dialoguer = "0.11"
figment = { workspace = true }
indicatif = "0.17"
koinon = { path = "../koinon" }
serde = { workspace = true }
serde_json = { workspace = true }
snafu = { workspace = true }
syntonia = { path = "../syntonia" }
tokio = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }

[lints]
workspace = true
53 changes: 53 additions & 0 deletions crates/akroasis/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! CLI definition — top-level subcommand tree.

use clap::{Parser, Subcommand};

use crate::radio::RadioCommand;

#[allow(clippy::doc_markdown)]
#[derive(Parser)]
#[command(name = "akroasis", version, about = "ἀκρόασις — attentive reception")]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}

/// Top-level subcommands.
#[derive(Subcommand)]
pub enum Command {
/// Radio management — frequency plans, programming, vehicle telemetry
Radio(RadioArgs),
/// Mesh networking — Meshtastic, topology, DTN, PACE communications
Mesh,
/// SDR reception — spectrum, demodulation, EW detection
Sdr,
/// Proximity — `WiFi`, BLE, Zigbee, NFC/RFID monitoring
Proximity,
/// Network defense — IDS/IPS, CAN bus, `IoT` security
Shield,
/// OSINT — feeds, recon, asset discovery, threat intel
Watch,
/// Offensive security — pentesting, vulnerability assessment
Test,
/// Signal intelligence — aggregation, correlation, focal points
Intel,
/// Automation — playbooks, PACE, state machines, triggers
Auto,
/// Navigation — vehicle/foot routing, military planning, maps
Nav,
/// Knowledge — offline references, frequency databases, manuals
Know,
/// Communications — encrypted messaging, key management
Comms,
/// Privacy — VPN, anonymization, OPSEC assessment
Privacy,
/// Serve the Akroasis daemon
Serve,
}

/// Radio subcommand arguments.
#[derive(clap::Args)]
pub struct RadioArgs {
#[command(subcommand)]
pub command: RadioCommand,
}
83 changes: 31 additions & 52 deletions crates/akroasis/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
//! RF intelligence, communications sovereignty, and operational awareness.
//! 17 crates. 10 capability domains. One shared signal model.

mod cli;
mod radio;

use std::path::PathBuf;

use clap::Parser;
Expand All @@ -13,6 +16,8 @@ use figment::{
use serde::Deserialize;
use snafu::{ResultExt, Snafu};

use cli::{Cli, Command};

/// Top-level application errors.
#[derive(Debug, Snafu)]
enum Error {
Expand All @@ -23,6 +28,10 @@ enum Error {
#[snafu(source(from(figment::Error, Box::new)))]
source: Box<figment::Error>,
},

/// A radio operation failed.
#[snafu(display("{source}"))]
Radio { source: radio::errors::RadioError },
}

/// Application configuration loaded from TOML file and environment overrides.
Expand All @@ -47,64 +56,34 @@ fn load_config() -> Result<Config, Error> {
.context(ConfigSnafu)
}

#[allow(clippy::doc_markdown)]
#[derive(Parser)]
#[command(name = "akroasis", version, about = "ἀκρόασις — attentive reception")]
enum Cli {
/// Radio management — frequency plans, programming, vehicle telemetry
Radio,
/// Mesh networking — Meshtastic, topology, DTN, PACE communications
Mesh,
/// SDR reception — spectrum, demodulation, EW detection
Sdr,
/// Proximity — WiFi, BLE, Zigbee, NFC/RFID monitoring
Proximity,
/// Network defense — IDS/IPS, CAN bus, IoT security
Shield,
/// OSINT — feeds, recon, asset discovery, threat intel
Watch,
/// Offensive security — pentesting, vulnerability assessment
Test,
/// Signal intelligence — aggregation, correlation, focal points
Intel,
/// Automation — playbooks, PACE, state machines, triggers
Auto,
/// Navigation — vehicle/foot routing, military planning, maps
Nav,
/// Knowledge — offline references, frequency databases, manuals
Know,
/// Communications — encrypted messaging, key management
Comms,
/// Privacy — VPN, anonymization, OPSEC assessment
Privacy,
/// Serve the Akroasis daemon
Serve,
}

fn dispatch(cli: &Cli) {
match cli {
Cli::Radio => println!("syntonia — radio management (not yet implemented)"),
Cli::Mesh => println!("kerykeion — mesh networking (not yet implemented)"),
Cli::Sdr => println!("dektis — SDR reception (not yet implemented)"),
Cli::Proximity => println!("engys — proximity intelligence (not yet implemented)"),
Cli::Shield => println!("aspis — network defense (not yet implemented)"),
Cli::Watch => println!("skopos — OSINT collection (not yet implemented)"),
Cli::Test => println!("peira — offensive security (not yet implemented)"),
Cli::Intel => println!("semaino + ichneutes — intelligence (not yet implemented)"),
Cli::Auto => println!("praxis — automation (not yet implemented)"),
Cli::Nav => println!("chorografia — navigation (not yet implemented)"),
Cli::Know => println!("pinax — knowledge repository (not yet implemented)"),
Cli::Comms => println!("kryphos — communications (not yet implemented)"),
Cli::Privacy => println!("lethe — privacy (not yet implemented)"),
Cli::Serve => println!("daemon mode (not yet implemented)"),
fn dispatch(command: &Command) -> Result<(), Error> {
match command {
Command::Radio(args) => {
radio::dispatch(&args.command).context(RadioSnafu)?;
}
Command::Mesh => println!("kerykeion — mesh networking (not yet implemented)"),
Command::Sdr => println!("dektis — SDR reception (not yet implemented)"),
Command::Proximity => println!("engys — proximity intelligence (not yet implemented)"),
Command::Shield => println!("aspis — network defense (not yet implemented)"),
Command::Watch => println!("skopos — OSINT collection (not yet implemented)"),
Command::Test => println!("peira — offensive security (not yet implemented)"),
Command::Intel => {
println!("semaino + ichneutes — intelligence (not yet implemented)");
}
Command::Auto => println!("praxis — automation (not yet implemented)"),
Command::Nav => println!("chorografia — navigation (not yet implemented)"),
Command::Know => println!("pinax — knowledge repository (not yet implemented)"),
Command::Comms => println!("kryphos — communications (not yet implemented)"),
Command::Privacy => println!("lethe — privacy (not yet implemented)"),
Command::Serve => println!("daemon mode (not yet implemented)"),
}
Ok(())
}

fn run() -> Result<(), Error> {
let _config = load_config()?;
let cli = Cli::parse();
dispatch(&cli);
Ok(())
dispatch(&cli.command)
}

fn main() {
Expand Down
95 changes: 95 additions & 0 deletions crates/akroasis/src/radio/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! `akroasis radio detect` — discover connected radios.

use super::errors::RadioError;
use super::{DetectedRadio, Hardware};

/// Runs the detect subcommand.
pub fn run(hw: &dyn Hardware) -> Result<(), RadioError> {
let radios = hw.detect_radios()?;

if radios.is_empty() {
println!(
"No radios detected. Check that the radio is on \
and the programming cable is connected."
);
return Ok(());
}

print_detected(&radios);
Ok(())
}

/// Formats and prints detected radios.
pub fn print_detected(radios: &[DetectedRadio]) {
println!("Detected radios:");
for (i, radio) in radios.iter().enumerate() {
let firmware_info = if radio.firmware.is_empty() {
String::new()
} else {
format!(" (firmware: {})", radio.firmware)
};
println!(
" {}. {} on {}{}",
i + 1,
radio.variant.display_name(),
radio.port,
firmware_info,
);
}

let warnings: Vec<&str> = radios
.iter()
.flat_map(|r| r.warnings.iter().map(String::as_str))
.collect();
for warning in warnings {
println!("\n\u{26a0} {warning}");
}
}

#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
mod tests {
use super::*;
use crate::radio::RadioVariant;

#[test]
fn format_single_detected_radio() {
let radios = vec![DetectedRadio {
variant: RadioVariant::BfF8hp,
port: "/dev/ttyUSB0".to_string(),
firmware: "BFP3V3".to_string(),
warnings: vec![],
}];

// Capture by calling print — we just verify it doesn't panic.
// The format is validated by the integration test.
print_detected(&radios);
}

#[test]
fn format_multiple_radios_with_warnings() {
let radios = vec![
DetectedRadio {
variant: RadioVariant::BfF8hp,
port: "/dev/ttyUSB0".to_string(),
firmware: "BFP3V3".to_string(),
warnings: vec![
"PL2303 clone detected on /dev/ttyUSB0 \u{2014} works on Linux, may fail on Windows.".to_string(),
],
},
DetectedRadio {
variant: RadioVariant::Uv5r,
port: "/dev/ttyUSB1".to_string(),
firmware: "BFB297".to_string(),
warnings: vec![],
},
];

print_detected(&radios);
}
}
Loading
Loading