use anyhow::{Context, Result};
use clap::Parser;
use serde_json::Value;
use crate::manifest::{loader, ManifestSource};
use crate::ui;
#[derive(Debug, Parser)]
pub struct ConnectorsArgs {}
pub fn run(_args: ConnectorsArgs) -> Result<()> {
ui::header(
"portaki connectors",
"Every declared egress, with what it needs to actually call.",
);
let module_root = std::env::current_dir().context("current_dir")?;
let (manifest, source) = loader::load(&module_root, None)?;
if matches!(source, ManifestSource::Emissions) {
ui::detail("read from the SDK emissions — no build output yet");
}
if manifest.connectors.custom.is_empty() && manifest.connectors.builtin.is_empty() {
ui::detail("this module declares no connector — it makes no outbound call");
ui::blank();
return Ok(());
}
for id in &manifest.connectors.builtin {
ui::wrote("builtin", id);
permission_line(id);
}
for connector in &manifest.connectors.custom {
let id = string_at(connector, "id").unwrap_or_default();
let base_url = string_at(connector, "baseUrl").unwrap_or_default();
ui::wrote(&id, &base_url);
for operation in connector
.get("operations")
.and_then(Value::as_array)
.unwrap_or(&Vec::new())
{
let name = string_at(operation, "id").unwrap_or_default();
let method = string_at(operation, "method").unwrap_or_else(|| "GET".to_string());
let path = string_at(operation, "path").unwrap_or_default();
ui::detail(format!("{name} {method} {path}"));
}
permission_line(&id);
let auth = string_at(connector, "auth")
.map(|declared| format!("auth {declared}"))
.unwrap_or_else(|| "auth derived from the provider".to_string());
match string_at(connector, "credentialProviderId") {
Some(provider) => ui::detail(format!("credential {provider} · {auth}")),
None => ui::detail(format!("credential none declared · {auth}")),
}
ui::advice("a call also needs that provider bound for the workspace — the dashboard, under Integrations");
}
ui::blank();
ui::success("every connector is declared, permitted, and named");
ui::blank();
Ok(())
}
fn permission_line(id: &str) {
ui::detail(format!("permission connectors:{id}"));
}
fn string_at(value: &Value, key: &str) -> Option<String> {
value.get(key)?.as_str().map(str::to_string)
}