use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use colored::Colorize;
fn main() {
vela_protocol::cli::register_scout_handler(scout_handler);
vela_protocol::cli::register_notes_handler(notes_handler);
vela_protocol::cli::register_code_handler(code_handler);
vela_protocol::cli::register_datasets_handler(datasets_handler);
vela_protocol::cli::register_reviewer_handler(reviewer_handler);
vela_protocol::cli::register_tensions_handler(tensions_handler);
vela_protocol::cli::register_experiments_handler(experiments_handler);
vela_protocol::cli::register_atlas_init_handler(atlas_init_handler);
vela_protocol::cli::register_atlas_materialize_handler(atlas_materialize_handler);
vela_protocol::cli::register_atlas_serve_handler(atlas_serve_handler);
vela_protocol::cli::register_atlas_update_handler(atlas_update_handler);
vela_protocol::cli::register_constellation_init_handler(constellation_init_handler);
vela_protocol::cli::register_constellation_materialize_handler(
constellation_materialize_handler,
);
vela_protocol::cli::register_constellation_serve_handler(constellation_serve_handler);
vela_protocol::cli::run_from_args();
}
fn atlas_init_handler(
atlases_root: PathBuf,
name: String,
domain: String,
scope_note: Option<String>,
frontiers: Vec<PathBuf>,
json: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
match vela_atlas::init_atlas(
&atlases_root,
&name,
&domain,
scope_note.as_deref(),
&frontiers,
) {
Ok((manifest_path, manifest)) => {
if json {
let payload = serde_json::json!({
"ok": true,
"command": "atlas.init",
"atlas_id": manifest.id,
"manifest_path": manifest_path.display().to_string(),
"frontier_count": manifest.composing_frontiers.len(),
});
println!(
"{}",
serde_json::to_string_pretty(&payload).expect("serialize atlas init")
);
} else {
println!();
println!(" {} {}", "ok".green(), manifest.id);
println!(" manifest: {}", manifest_path.display());
println!(
" composing frontiers: {}",
manifest.composing_frontiers.len()
);
for fr in &manifest.composing_frontiers {
println!(" · {} ({})", fr.name, fr.vfr_id);
}
}
}
Err(e) => {
eprintln!("{} atlas init: {e}", "err ·".red());
std::process::exit(1);
}
}
})
}
fn atlas_materialize_handler(
atlases_root: PathBuf,
name: String,
json: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
let atlas_dir = atlases_root.join(&name);
match vela_atlas::materialize_atlas(&atlas_dir) {
Ok((snapshot_path, snapshot)) => {
if json {
let payload = serde_json::json!({
"ok": true,
"command": "atlas.materialize",
"atlas_id": snapshot.atlas_id,
"snapshot_path": snapshot_path.display().to_string(),
"frontier_count": snapshot.frontier_count,
"total_findings": snapshot.total_findings,
"accepted_core_findings": snapshot.accepted_core_findings,
"total_events": snapshot.total_events,
"composition_hash": snapshot.composition_hash,
});
println!(
"{}",
serde_json::to_string_pretty(&payload)
.expect("serialize atlas materialize")
);
} else {
println!();
println!(" {} {}", "atlas".green(), snapshot.atlas_name);
println!(" vat_id: {}", snapshot.atlas_id);
println!(" domain: {}", snapshot.domain);
println!(" frontiers: {}", snapshot.frontier_count);
println!(" total findings: {}", snapshot.total_findings);
println!(" accepted-core: {}", snapshot.accepted_core_findings);
println!(" total events: {}", snapshot.total_events);
println!(" composition hash: {}", snapshot.composition_hash);
println!(" snapshot: {}", snapshot_path.display());
for fr in &snapshot.frontiers {
println!(
" · {} ({}): {} findings, {} events",
fr.name, fr.vfr_id, fr.findings, fr.events
);
}
}
}
Err(e) => {
eprintln!("{} atlas materialize: {e}", "err ·".red());
std::process::exit(1);
}
}
})
}
fn atlas_update_handler(
atlases_root: PathBuf,
name: String,
add_frontier: Vec<PathBuf>,
remove_vfr_id: Vec<String>,
json: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
let atlas_dir = atlases_root.join(&name);
match vela_atlas::update_atlas(&atlas_dir, &add_frontier, &remove_vfr_id) {
Ok((manifest_path, manifest)) => {
if json {
let payload = serde_json::json!({
"ok": true,
"command": "atlas.update",
"atlas_id": manifest.id,
"manifest_path": manifest_path.display().to_string(),
"frontier_count": manifest.composing_frontiers.len(),
"added": add_frontier.len(),
"removed": remove_vfr_id.len(),
});
println!(
"{}",
serde_json::to_string_pretty(&payload).expect("serialize atlas update")
);
} else {
println!();
println!(" {} {}", "ok".green(), manifest.id);
println!(" manifest: {}", manifest_path.display());
println!(
" composing frontiers: {}",
manifest.composing_frontiers.len()
);
println!(" added: {}", add_frontier.len());
println!(" removed: {}", remove_vfr_id.len());
for fr in &manifest.composing_frontiers {
println!(" · {} ({})", fr.name, fr.vfr_id);
}
}
}
Err(e) => {
eprintln!("{} atlas update: {e}", "err ·".red());
std::process::exit(1);
}
}
})
}
fn atlas_serve_handler(
atlases_root: PathBuf,
name: String,
port: u16,
open_browser: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
let atlas_dir = atlases_root.join(&name);
match vela_atlas::materialize_atlas(&atlas_dir) {
Ok((_path, snapshot)) => {
println!();
println!(" {} {}", "atlas".green(), snapshot.atlas_name);
println!(" vat_id: {}", snapshot.atlas_id);
println!(" composing frontiers: {}", snapshot.frontier_count);
println!(" total findings: {}", snapshot.total_findings);
println!(" bridges (manifest): {}", snapshot.bridge_count);
println!();
}
Err(e) => {
eprintln!("{} atlas materialize: {e}", "err ·".red());
std::process::exit(1);
}
}
use axum::Router;
use tower_http::services::ServeDir;
let serve_dir = ServeDir::new(&atlas_dir);
let app: Router = Router::new().fallback_service(serve_dir);
let addr = format!("127.0.0.1:{port}");
let listener = match tokio::net::TcpListener::bind(&addr).await {
Ok(l) => l,
Err(e) => {
eprintln!("{} bind {addr}: {e}", "err ·".red());
std::process::exit(1);
}
};
let url = format!("http://{addr}/");
println!(" {} {}", "serving atlas at".green(), url);
println!();
if open_browser && let Err(e) = std::process::Command::new("open").arg(&url).spawn() {
eprintln!(" (note: could not auto-open browser: {e})");
}
if let Err(e) = axum::serve(listener, app).await {
eprintln!("{} axum::serve: {e}", "err ·".red());
std::process::exit(1);
}
})
}
fn scout_handler(
folder: PathBuf,
frontier: PathBuf,
backend: Option<String>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::scout::{ScoutInput, run};
let model = backend.and_then(|b| {
let trimmed = b.trim().to_string();
if trimmed.is_empty() || trimmed == "claude-cli" || trimmed == "default" {
None
} else {
Some(trimmed)
}
});
let input = ScoutInput {
folder: folder.clone(),
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(" {}", "VELA · SCOUT · LITERATURE".dimmed());
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(
" model: {}",
if report.run.model.is_empty() {
"(env default)"
} else {
&report.run.model
}
);
println!(" folder: {}", folder.display());
println!(" frontier: {}", frontier.display());
println!(" pdfs seen: {}", report.pdfs_seen);
println!(" pdfs processed: {}", report.pdfs_processed);
println!(" candidates: {}", report.candidates_emitted);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {} files", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.path, s.reason);
}
if report.skipped.len() > 5 {
println!(" … {} more", report.skipped.len() - 5);
}
}
println!();
if !dry_run && report.proposals_written > 0 {
println!(
" next: review in the Workbench Inbox, then `vela queue sign --all`."
);
}
}
Err(e) => {
eprintln!(" scout failed: {e}");
std::process::exit(1);
}
}
})
}
fn notes_handler(
vault: PathBuf,
frontier: PathBuf,
backend: Option<String>,
max_files: Option<usize>,
max_items_per_category: Option<usize>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::notes::{NotesInput, run};
let model = backend.and_then(|b| {
let trimmed = b.trim().to_string();
if trimmed.is_empty() || trimmed == "claude-cli" || trimmed == "default" {
None
} else {
Some(trimmed)
}
});
let input = NotesInput {
vault: vault.clone(),
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
max_files: max_files.or(Some(50)),
max_items_per_category: max_items_per_category.or(Some(4)),
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(" {}", "VELA · COMPILE-NOTES · NOTES-COMPILER".dimmed());
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(
" model: {}",
if report.run.model.is_empty() {
"(env default)"
} else {
&report.run.model
}
);
println!(" vault: {}", vault.display());
println!(" frontier: {}", frontier.display());
println!(" notes seen: {}", report.notes_seen);
println!(" notes processed: {}", report.notes_processed);
println!(" open questions: {}", report.open_questions_emitted);
println!(" hypotheses: {}", report.hypotheses_emitted);
println!(
" candidate findings: {}",
report.candidate_findings_emitted
);
println!(" tensions: {}", report.tensions_emitted);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {} files", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.path, s.reason);
}
if report.skipped.len() > 5 {
println!(" … {} more", report.skipped.len() - 5);
}
}
println!();
if !dry_run && report.proposals_written > 0 {
println!(
" next: review in the Workbench Inbox, then `vela queue sign --all`."
);
}
}
Err(e) => {
eprintln!(" notes compiler failed: {e}");
std::process::exit(1);
}
}
})
}
fn code_handler(
root: PathBuf,
frontier: PathBuf,
backend: Option<String>,
max_files: Option<usize>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::code_analyst::{CodeAnalystInput, run};
let model = backend.and_then(|b| {
let trimmed = b.trim().to_string();
if trimmed.is_empty() || trimmed == "claude-cli" || trimmed == "default" {
None
} else {
Some(trimmed)
}
});
let input = CodeAnalystInput {
root: root.clone(),
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
max_files: max_files.or(Some(30)),
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(" {}", "VELA · COMPILE-CODE · CODE-ANALYST".dimmed());
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(
" model: {}",
if report.run.model.is_empty() {
"(env default)"
} else {
&report.run.model
}
);
println!(" root: {}", root.display());
println!(" frontier: {}", frontier.display());
println!(" files seen: {}", report.files_seen);
println!(" notebooks processed: {}", report.notebooks_processed);
println!(" scripts processed: {}", report.scripts_processed);
println!(" analyses: {}", report.analyses_emitted);
println!(" code findings: {}", report.code_findings_emitted);
println!(
" experiment intents: {}",
report.experiment_intents_emitted
);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {} files", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.path, s.reason);
}
if report.skipped.len() > 5 {
println!(" … {} more", report.skipped.len() - 5);
}
}
println!();
if !dry_run && report.proposals_written > 0 {
println!(
" next: review in the Workbench Inbox, then `vela queue sign --all`."
);
}
}
Err(e) => {
eprintln!(" code analyst failed: {e}");
std::process::exit(1);
}
}
})
}
fn datasets_handler(
root: PathBuf,
frontier: PathBuf,
backend: Option<String>,
sample_rows: Option<usize>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::datasets::{DatasetInput, run};
let model = backend.and_then(|b| {
let trimmed = b.trim().to_string();
if trimmed.is_empty() || trimmed == "claude-cli" || trimmed == "default" {
None
} else {
Some(trimmed)
}
});
let input = DatasetInput {
root: root.clone(),
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
sample_rows: sample_rows.unwrap_or(50),
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(" {}", "VELA · COMPILE-DATA · DATASETS".dimmed());
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(
" model: {}",
if report.run.model.is_empty() {
"(env default)"
} else {
&report.run.model
}
);
println!(" root: {}", root.display());
println!(" frontier: {}", frontier.display());
println!(" datasets seen: {}", report.datasets_seen);
println!(" csv processed: {}", report.csv_processed);
println!(" parquet processed: {}", report.parquet_processed);
println!(
" dataset summaries: {}",
report.dataset_summaries_emitted
);
println!(
" supported claims: {}",
report.supported_claims_emitted
);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {} files", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.path, s.reason);
}
if report.skipped.len() > 5 {
println!(" … {} more", report.skipped.len() - 5);
}
}
println!();
if !dry_run && report.proposals_written > 0 {
println!(
" next: review in the Workbench Inbox, then `vela queue sign --all`."
);
}
}
Err(e) => {
eprintln!(" datasets agent failed: {e}");
std::process::exit(1);
}
}
})
}
fn reviewer_handler(
frontier: PathBuf,
backend: Option<String>,
max_proposals: Option<usize>,
batch_size: usize,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::reviewer::{ReviewerInput, run};
let model = backend.and_then(|b| {
let t = b.trim().to_string();
if t.is_empty() || t == "claude-cli" || t == "default" {
None
} else {
Some(t)
}
});
let input = ReviewerInput {
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
max_proposals: max_proposals.or(Some(30)),
batch_size,
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(" {}", "VELA · REVIEW-PENDING · REVIEWER-AGENT".dimmed());
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(" frontier: {}", frontier.display());
println!(" pending seen: {}", report.pending_seen);
println!(" scored: {}", report.scored);
println!(
" notes: {} {}",
report.notes_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {}", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.proposal_id, s.reason);
}
}
println!();
}
Err(e) => {
eprintln!(" reviewer agent failed: {e}");
std::process::exit(1);
}
}
})
}
fn tensions_handler(
frontier: PathBuf,
backend: Option<String>,
max_findings: Option<usize>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::tensions::{TensionsInput, run};
let model = backend.and_then(|b| {
let t = b.trim().to_string();
if t.is_empty() || t == "claude-cli" || t == "default" {
None
} else {
Some(t)
}
});
let input = TensionsInput {
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
max_findings: max_findings.or(Some(60)),
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(
" {}",
"VELA · FIND-TENSIONS · CONTRADICTION-FINDER".dimmed()
);
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(" frontier: {}", frontier.display());
println!(" findings seen: {}", report.findings_seen);
println!(" batches processed: {}", report.batches_processed);
println!(" tensions emitted: {}", report.tensions_emitted);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped batches: {}", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - batch {}: {}", s.batch, s.reason);
}
}
println!();
}
Err(e) => {
eprintln!(" contradiction finder failed: {e}");
std::process::exit(1);
}
}
})
}
fn experiments_handler(
frontier: PathBuf,
backend: Option<String>,
max_findings: Option<usize>,
dry_run: bool,
json_out: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
use vela_scientist::experiments::{ExperimentsInput, run};
let model = backend.and_then(|b| {
let t = b.trim().to_string();
if t.is_empty() || t == "claude-cli" || t == "default" {
None
} else {
Some(t)
}
});
let input = ExperimentsInput {
frontier_path: frontier.clone(),
model,
cli_command: std::env::var("VELA_SCIENTIST_CLI")
.unwrap_or_else(|_| "claude".to_string()),
apply: !dry_run,
max_findings: max_findings.or(Some(20)),
};
match run(input).await {
Ok(report) => {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
return;
}
println!();
println!(
" {}",
"VELA · PLAN-EXPERIMENTS · EXPERIMENT-PLANNER".dimmed()
);
println!(" {}", tick_row(60));
println!(" agent: {}", report.run.agent);
println!(" run id: {}", report.run.run_id);
println!(" frontier: {}", frontier.display());
println!(" questions seen: {}", report.questions_seen);
println!(" hypotheses seen: {}", report.hypotheses_seen);
println!(" experiments emitted: {}", report.experiments_emitted);
println!(
" proposals: {} {}",
report.proposals_written,
if dry_run {
"(dry-run, not written)"
} else {
"(appended to frontier)"
}
);
if !report.skipped.is_empty() {
println!(" skipped: {}", report.skipped.len());
for s in report.skipped.iter().take(5) {
println!(" - {}: {}", s.finding_id, s.reason);
}
}
println!();
}
Err(e) => {
eprintln!(" experiment planner failed: {e}");
std::process::exit(1);
}
}
})
}
fn tick_row(width: usize) -> String {
let mut out = String::with_capacity(width);
for i in 0..width {
out.push(if i % 4 == 0 { '·' } else { ' ' });
}
out
}
fn constellation_init_handler(
constellations_root: PathBuf,
name: String,
scope_note: Option<String>,
atlases: Vec<PathBuf>,
json: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
match vela_constellation::init_constellation(
&constellations_root,
&name,
scope_note.as_deref(),
&atlases,
) {
Ok((manifest_path, manifest)) => {
if json {
let payload = serde_json::json!({
"ok": true,
"command": "constellation.init",
"constellation_id": manifest.id,
"manifest_path": manifest_path.display().to_string(),
"atlas_count": manifest.composing_atlases.len(),
});
println!(
"{}",
serde_json::to_string_pretty(&payload)
.expect("serialize constellation init")
);
} else {
println!();
println!(" {} {}", "ok".green(), manifest.id);
println!(" manifest: {}", manifest_path.display());
println!(" composing atlases: {}", manifest.composing_atlases.len());
for a in &manifest.composing_atlases {
println!(" · {} ({})", a.name, a.vat_id);
}
}
}
Err(e) => {
eprintln!("{} constellation init: {e}", "err ·".red());
std::process::exit(1);
}
}
})
}
fn constellation_materialize_handler(
constellations_root: PathBuf,
name: String,
json: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
let dir = constellations_root.join(&name);
match vela_constellation::materialize_constellation(&dir) {
Ok((snapshot_path, snapshot)) => {
if json {
let payload = serde_json::json!({
"ok": true,
"command": "constellation.materialize",
"constellation_id": snapshot.constellation_id,
"snapshot_path": snapshot_path.display().to_string(),
"atlas_count": snapshot.atlas_count,
"total_frontiers": snapshot.total_frontiers,
"total_findings": snapshot.total_findings,
"total_accepted_core": snapshot.total_accepted_core,
"total_events": snapshot.total_events,
"total_bridges": snapshot.total_bridges,
"cross_atlas_bridges": snapshot.cross_atlas_bridges,
"composition_hash": snapshot.composition_hash,
});
println!(
"{}",
serde_json::to_string_pretty(&payload)
.expect("serialize constellation materialize")
);
} else {
println!();
println!(
" {} {}",
"constellation".green(),
snapshot.constellation_name
);
println!(" vco_id: {}", snapshot.constellation_id);
println!(" atlases: {}", snapshot.atlas_count);
println!(" total frontiers: {}", snapshot.total_frontiers);
println!(" total findings: {}", snapshot.total_findings);
println!(" accepted-core: {}", snapshot.total_accepted_core);
println!(" total events: {}", snapshot.total_events);
println!(" total bridges: {}", snapshot.total_bridges);
println!(" cross-Atlas bridges: {}", snapshot.cross_atlas_bridges);
println!(" composition hash: {}", snapshot.composition_hash);
println!(" snapshot: {}", snapshot_path.display());
for a in &snapshot.atlases {
println!(
" · {} ({}): {} frontiers, {} findings, {} events, {} bridges",
a.name, a.vat_id, a.frontiers, a.findings, a.events, a.bridges
);
}
}
}
Err(e) => {
eprintln!("{} constellation materialize: {e}", "err ·".red());
std::process::exit(1);
}
}
})
}
fn constellation_serve_handler(
constellations_root: PathBuf,
name: String,
port: u16,
open_browser: bool,
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async move {
let dir = constellations_root.join(&name);
match vela_constellation::materialize_constellation(&dir) {
Ok((_, snapshot)) => {
println!();
println!(
" {} {}",
"constellation".green(),
snapshot.constellation_name
);
println!(" vco_id: {}", snapshot.constellation_id);
println!(" atlases: {}", snapshot.atlas_count);
println!(" total findings: {}", snapshot.total_findings);
println!();
}
Err(e) => {
eprintln!("{} constellation materialize: {e}", "err ·".red());
std::process::exit(1);
}
}
use axum::Router;
use tower_http::services::ServeDir;
let serve_dir = ServeDir::new(&dir);
let app: Router = Router::new().fallback_service(serve_dir);
let addr = format!("127.0.0.1:{port}");
let listener = match tokio::net::TcpListener::bind(&addr).await {
Ok(l) => l,
Err(e) => {
eprintln!("{} bind {addr}: {e}", "err ·".red());
std::process::exit(1);
}
};
let url = format!("http://{addr}/");
println!(" {} {}", "serving constellation at".green(), url);
println!();
if open_browser && let Err(e) = std::process::Command::new("open").arg(&url).spawn() {
eprintln!(" (note: could not auto-open browser: {e})");
}
if let Err(e) = axum::serve(listener, app).await {
eprintln!("{} axum::serve: {e}", "err ·".red());
std::process::exit(1);
}
})
}