feat: initial release — ZeroClaw v0.1.0
- 22 AI providers (OpenRouter, Anthropic, OpenAI, Mistral, etc.) - 7 channels (CLI, Telegram, Discord, Slack, iMessage, Matrix, Webhook) - 5-step onboarding wizard with Project Context personalization - OpenClaw-aligned system prompt (SOUL.md, IDENTITY.md, USER.md, AGENTS.md, etc.) - SQLite memory backend with auto-save - Skills system with on-demand loading - Security: autonomy levels, command allowlists, cost limits - 532 tests passing, 0 clippy warnings
This commit is contained in:
commit
05cb353f7f
71 changed files with 15757 additions and 0 deletions
234
src/integrations/mod.rs
Normal file
234
src/integrations/mod.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
pub mod registry;
|
||||
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Integration status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IntegrationStatus {
|
||||
/// Fully implemented and ready to use
|
||||
Available,
|
||||
/// Configured and active
|
||||
Active,
|
||||
/// Planned but not yet implemented
|
||||
ComingSoon,
|
||||
}
|
||||
|
||||
/// Integration category
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IntegrationCategory {
|
||||
Chat,
|
||||
AiModel,
|
||||
Productivity,
|
||||
MusicAudio,
|
||||
SmartHome,
|
||||
ToolsAutomation,
|
||||
MediaCreative,
|
||||
Social,
|
||||
Platform,
|
||||
}
|
||||
|
||||
impl IntegrationCategory {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Chat => "Chat Providers",
|
||||
Self::AiModel => "AI Models",
|
||||
Self::Productivity => "Productivity",
|
||||
Self::MusicAudio => "Music & Audio",
|
||||
Self::SmartHome => "Smart Home",
|
||||
Self::ToolsAutomation => "Tools & Automation",
|
||||
Self::MediaCreative => "Media & Creative",
|
||||
Self::Social => "Social",
|
||||
Self::Platform => "Platforms",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> &'static [Self] {
|
||||
&[
|
||||
Self::Chat,
|
||||
Self::AiModel,
|
||||
Self::Productivity,
|
||||
Self::MusicAudio,
|
||||
Self::SmartHome,
|
||||
Self::ToolsAutomation,
|
||||
Self::MediaCreative,
|
||||
Self::Social,
|
||||
Self::Platform,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// A registered integration
|
||||
pub struct IntegrationEntry {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
pub category: IntegrationCategory,
|
||||
pub status_fn: fn(&Config) -> IntegrationStatus,
|
||||
}
|
||||
|
||||
/// Handle the `integrations` CLI command
|
||||
pub fn handle_command(command: super::IntegrationCommands, config: &Config) -> Result<()> {
|
||||
match command {
|
||||
super::IntegrationCommands::List { category } => {
|
||||
list_integrations(config, category.as_deref())
|
||||
}
|
||||
super::IntegrationCommands::Info { name } => show_integration_info(config, &name),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn list_integrations(config: &Config, filter_category: Option<&str>) -> Result<()> {
|
||||
let entries = registry::all_integrations();
|
||||
|
||||
let mut available = 0u32;
|
||||
let mut active = 0u32;
|
||||
let mut coming = 0u32;
|
||||
|
||||
for &cat in IntegrationCategory::all() {
|
||||
// Filter by category if specified
|
||||
if let Some(filter) = filter_category {
|
||||
let filter_lower = filter.to_lowercase();
|
||||
let cat_lower = cat.label().to_lowercase();
|
||||
if !cat_lower.contains(&filter_lower) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let cat_entries: Vec<&IntegrationEntry> =
|
||||
entries.iter().filter(|e| e.category == cat).collect();
|
||||
|
||||
if cat_entries.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("\n ⟩ {}", console::style(cat.label()).white().bold());
|
||||
|
||||
for entry in &cat_entries {
|
||||
let status = (entry.status_fn)(config);
|
||||
let (icon, label) = match status {
|
||||
IntegrationStatus::Active => {
|
||||
active += 1;
|
||||
("✅", console::style("active").green())
|
||||
}
|
||||
IntegrationStatus::Available => {
|
||||
available += 1;
|
||||
("⚪", console::style("available").dim())
|
||||
}
|
||||
IntegrationStatus::ComingSoon => {
|
||||
coming += 1;
|
||||
("🔜", console::style("coming soon").dim())
|
||||
}
|
||||
};
|
||||
println!(
|
||||
" {icon} {:<22} {:<30} {}",
|
||||
console::style(entry.name).white().bold(),
|
||||
entry.description,
|
||||
label
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let total = available + active + coming;
|
||||
println!();
|
||||
println!(" {total} integrations: {active} active, {available} available, {coming} coming soon");
|
||||
println!();
|
||||
println!(" Configure: zeroclaw onboard");
|
||||
println!(" Details: zeroclaw integrations info <name>");
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_integration_info(config: &Config, name: &str) -> Result<()> {
|
||||
let entries = registry::all_integrations();
|
||||
let name_lower = name.to_lowercase();
|
||||
|
||||
let Some(entry) = entries.iter().find(|e| e.name.to_lowercase() == name_lower) else {
|
||||
anyhow::bail!(
|
||||
"Unknown integration: {name}. Run `zeroclaw integrations list` to see all."
|
||||
);
|
||||
};
|
||||
|
||||
let status = (entry.status_fn)(config);
|
||||
let (icon, label) = match status {
|
||||
IntegrationStatus::Active => ("✅", "Active"),
|
||||
IntegrationStatus::Available => ("⚪", "Available"),
|
||||
IntegrationStatus::ComingSoon => ("🔜", "Coming Soon"),
|
||||
};
|
||||
|
||||
println!();
|
||||
println!(" {} {} — {}", icon, console::style(entry.name).white().bold(), entry.description);
|
||||
println!(" Category: {}", entry.category.label());
|
||||
println!(" Status: {label}");
|
||||
println!();
|
||||
|
||||
// Show setup hints based on integration
|
||||
match entry.name {
|
||||
"Telegram" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Message @BotFather on Telegram");
|
||||
println!(" 2. Create a bot and copy the token");
|
||||
println!(" 3. Run: zeroclaw onboard");
|
||||
println!(" 4. Start: zeroclaw channel start");
|
||||
}
|
||||
"Discord" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Go to https://discord.com/developers/applications");
|
||||
println!(" 2. Create app → Bot → Copy token");
|
||||
println!(" 3. Enable MESSAGE CONTENT intent");
|
||||
println!(" 4. Run: zeroclaw onboard");
|
||||
}
|
||||
"Slack" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Go to https://api.slack.com/apps");
|
||||
println!(" 2. Create app → Bot Token Scopes → Install");
|
||||
println!(" 3. Run: zeroclaw onboard");
|
||||
}
|
||||
"OpenRouter" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Get API key at https://openrouter.ai/keys");
|
||||
println!(" 2. Run: zeroclaw onboard");
|
||||
println!(" Access 200+ models with one key.");
|
||||
}
|
||||
"Ollama" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Install: brew install ollama");
|
||||
println!(" 2. Pull a model: ollama pull llama3");
|
||||
println!(" 3. Set provider to 'ollama' in config.toml");
|
||||
}
|
||||
"iMessage" => {
|
||||
println!(" Setup (macOS only):");
|
||||
println!(" Uses AppleScript bridge to send/receive iMessages.");
|
||||
println!(" Requires Full Disk Access in System Settings → Privacy.");
|
||||
}
|
||||
"GitHub" => {
|
||||
println!(" Setup:");
|
||||
println!(" 1. Create a personal access token at https://github.com/settings/tokens");
|
||||
println!(" 2. Add to config: [integrations.github] token = \"ghp_...\"");
|
||||
}
|
||||
"Browser" => {
|
||||
println!(" Built-in:");
|
||||
println!(" ZeroClaw can control Chrome/Chromium for web tasks.");
|
||||
println!(" Uses headless browser automation.");
|
||||
}
|
||||
"Cron" => {
|
||||
println!(" Built-in:");
|
||||
println!(" Schedule tasks in ~/.zeroclaw/workspace/cron/");
|
||||
println!(" Run: zeroclaw cron list");
|
||||
}
|
||||
"Webhooks" => {
|
||||
println!(" Built-in:");
|
||||
println!(" HTTP endpoint for external triggers.");
|
||||
println!(" Run: zeroclaw gateway");
|
||||
}
|
||||
_ => {
|
||||
if status == IntegrationStatus::ComingSoon {
|
||||
println!(" This integration is planned. Stay tuned!");
|
||||
println!(" Track progress: https://github.com/theonlyhennygod/zeroclaw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
821
src/integrations/registry.rs
Normal file
821
src/integrations/registry.rs
Normal file
|
|
@ -0,0 +1,821 @@
|
|||
use super::{IntegrationCategory, IntegrationEntry, IntegrationStatus};
|
||||
|
||||
/// Returns the full catalog of integrations
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn all_integrations() -> Vec<IntegrationEntry> {
|
||||
vec![
|
||||
// ── Chat Providers ──────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Telegram",
|
||||
description: "Bot API — long-polling",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.telegram.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Discord",
|
||||
description: "Servers, channels & DMs",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.discord.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Slack",
|
||||
description: "Workspace apps via Web API",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.slack.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Webhooks",
|
||||
description: "HTTP endpoint for triggers",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.webhook.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "WhatsApp",
|
||||
description: "QR pairing via web bridge",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Signal",
|
||||
description: "Privacy-focused via signal-cli",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "iMessage",
|
||||
description: "macOS AppleScript bridge",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.imessage.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Microsoft Teams",
|
||||
description: "Enterprise chat support",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Matrix",
|
||||
description: "Matrix protocol (Element)",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |c| {
|
||||
if c.channels_config.matrix.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Nostr",
|
||||
description: "Decentralized DMs (NIP-04)",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "WebChat",
|
||||
description: "Browser-based chat UI",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Nextcloud Talk",
|
||||
description: "Self-hosted Nextcloud chat",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Zalo",
|
||||
description: "Zalo Bot API",
|
||||
category: IntegrationCategory::Chat,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── AI Models ───────────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "OpenRouter",
|
||||
description: "200+ models, 1 API key",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("openrouter") && c.api_key.is_some() {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Anthropic",
|
||||
description: "Claude 3.5/4 Sonnet & Opus",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("anthropic") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "OpenAI",
|
||||
description: "GPT-4o, GPT-5, o1",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("openai") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Google",
|
||||
description: "Gemini 2.5 Pro/Flash",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_model.as_deref().is_some_and(|m| m.starts_with("google/")) {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "DeepSeek",
|
||||
description: "DeepSeek V3 & R1",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_model.as_deref().is_some_and(|m| m.starts_with("deepseek/")) {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "xAI",
|
||||
description: "Grok 3 & 4",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_model.as_deref().is_some_and(|m| m.starts_with("x-ai/")) {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Mistral",
|
||||
description: "Mistral Large & Codestral",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_model.as_deref().is_some_and(|m| m.starts_with("mistral")) {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Ollama",
|
||||
description: "Local models (Llama, etc.)",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("ollama") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Perplexity",
|
||||
description: "Search-augmented AI",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("perplexity") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Hugging Face",
|
||||
description: "Open-source models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "LM Studio",
|
||||
description: "Local model server",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Venice",
|
||||
description: "Privacy-first inference (Llama, Opus)",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("venice") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Vercel AI",
|
||||
description: "Vercel AI Gateway",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("vercel") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Cloudflare AI",
|
||||
description: "Cloudflare AI Gateway",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("cloudflare") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Moonshot",
|
||||
description: "Kimi & Kimi Coding",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("moonshot") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Synthetic",
|
||||
description: "Synthetic AI models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("synthetic") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "OpenCode Zen",
|
||||
description: "Code-focused AI models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("opencode") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Z.AI",
|
||||
description: "Z.AI inference",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("zai") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "GLM",
|
||||
description: "ChatGLM / Zhipu models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("glm") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "MiniMax",
|
||||
description: "MiniMax AI models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("minimax") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Amazon Bedrock",
|
||||
description: "AWS managed model access",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("bedrock") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Qianfan",
|
||||
description: "Baidu AI models",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("qianfan") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Groq",
|
||||
description: "Ultra-fast LPU inference",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("groq") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Together AI",
|
||||
description: "Open-source model hosting",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("together") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Fireworks AI",
|
||||
description: "Fast open-source inference",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("fireworks") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Cohere",
|
||||
description: "Command R+ & embeddings",
|
||||
category: IntegrationCategory::AiModel,
|
||||
status_fn: |c| {
|
||||
if c.default_provider.as_deref() == Some("cohere") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
// ── Productivity ────────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "GitHub",
|
||||
description: "Code, issues, PRs",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Notion",
|
||||
description: "Workspace & databases",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Apple Notes",
|
||||
description: "Native macOS/iOS notes",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Apple Reminders",
|
||||
description: "Task management",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Obsidian",
|
||||
description: "Knowledge graph notes",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Things 3",
|
||||
description: "GTD task manager",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Bear Notes",
|
||||
description: "Markdown notes",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Trello",
|
||||
description: "Kanban boards",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Linear",
|
||||
description: "Issue tracking",
|
||||
category: IntegrationCategory::Productivity,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Music & Audio ───────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Spotify",
|
||||
description: "Music playback control",
|
||||
category: IntegrationCategory::MusicAudio,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Sonos",
|
||||
description: "Multi-room audio",
|
||||
category: IntegrationCategory::MusicAudio,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Shazam",
|
||||
description: "Song recognition",
|
||||
category: IntegrationCategory::MusicAudio,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Smart Home ──────────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Home Assistant",
|
||||
description: "Home automation hub",
|
||||
category: IntegrationCategory::SmartHome,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Philips Hue",
|
||||
description: "Smart lighting",
|
||||
category: IntegrationCategory::SmartHome,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "8Sleep",
|
||||
description: "Smart mattress",
|
||||
category: IntegrationCategory::SmartHome,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Tools & Automation ──────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Browser",
|
||||
description: "Chrome/Chromium control",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::Available,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Shell",
|
||||
description: "Terminal command execution",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::Active,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "File System",
|
||||
description: "Read/write files",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::Active,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Cron",
|
||||
description: "Scheduled tasks",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::Available,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Voice",
|
||||
description: "Voice wake + talk mode",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Gmail",
|
||||
description: "Email triggers & send",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "1Password",
|
||||
description: "Secure credentials",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Weather",
|
||||
description: "Forecasts & conditions",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Canvas",
|
||||
description: "Visual workspace + A2UI",
|
||||
category: IntegrationCategory::ToolsAutomation,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Media & Creative ────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Image Gen",
|
||||
description: "AI image generation",
|
||||
category: IntegrationCategory::MediaCreative,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "GIF Search",
|
||||
description: "Find the perfect GIF",
|
||||
category: IntegrationCategory::MediaCreative,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Screen Capture",
|
||||
description: "Screenshot & screen control",
|
||||
category: IntegrationCategory::MediaCreative,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Camera",
|
||||
description: "Photo/video capture",
|
||||
category: IntegrationCategory::MediaCreative,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Social ──────────────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "Twitter/X",
|
||||
description: "Tweet, reply, search",
|
||||
category: IntegrationCategory::Social,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Email",
|
||||
description: "Send & read emails",
|
||||
category: IntegrationCategory::Social,
|
||||
status_fn: |_| IntegrationStatus::ComingSoon,
|
||||
},
|
||||
// ── Platforms ───────────────────────────────────────────
|
||||
IntegrationEntry {
|
||||
name: "macOS",
|
||||
description: "Native support + AppleScript",
|
||||
category: IntegrationCategory::Platform,
|
||||
status_fn: |_| {
|
||||
if cfg!(target_os = "macos") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Linux",
|
||||
description: "Native support",
|
||||
category: IntegrationCategory::Platform,
|
||||
status_fn: |_| {
|
||||
if cfg!(target_os = "linux") {
|
||||
IntegrationStatus::Active
|
||||
} else {
|
||||
IntegrationStatus::Available
|
||||
}
|
||||
},
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Windows",
|
||||
description: "WSL2 recommended",
|
||||
category: IntegrationCategory::Platform,
|
||||
status_fn: |_| IntegrationStatus::Available,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "iOS",
|
||||
description: "Chat via Telegram/Discord",
|
||||
category: IntegrationCategory::Platform,
|
||||
status_fn: |_| IntegrationStatus::Available,
|
||||
},
|
||||
IntegrationEntry {
|
||||
name: "Android",
|
||||
description: "Chat via Telegram/Discord",
|
||||
category: IntegrationCategory::Platform,
|
||||
status_fn: |_| IntegrationStatus::Available,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::config::schema::{
|
||||
ChannelsConfig, IMessageConfig, MatrixConfig, TelegramConfig,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn registry_has_entries() {
|
||||
let entries = all_integrations();
|
||||
assert!(entries.len() >= 50, "Expected 50+ integrations, got {}", entries.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_categories_represented() {
|
||||
let entries = all_integrations();
|
||||
for cat in IntegrationCategory::all() {
|
||||
let count = entries.iter().filter(|e| e.category == *cat).count();
|
||||
assert!(count > 0, "Category {:?} has no entries", cat);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_functions_dont_panic() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
for entry in &entries {
|
||||
let _ = (entry.status_fn)(&config);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicate_names() {
|
||||
let entries = all_integrations();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for entry in &entries {
|
||||
assert!(
|
||||
seen.insert(entry.name),
|
||||
"Duplicate integration name: {}",
|
||||
entry.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_empty_names_or_descriptions() {
|
||||
let entries = all_integrations();
|
||||
for entry in &entries {
|
||||
assert!(!entry.name.is_empty(), "Found integration with empty name");
|
||||
assert!(
|
||||
!entry.description.is_empty(),
|
||||
"Integration '{}' has empty description",
|
||||
entry.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telegram_active_when_configured() {
|
||||
let mut config = Config::default();
|
||||
config.channels_config.telegram = Some(TelegramConfig {
|
||||
bot_token: "123:ABC".into(),
|
||||
allowed_users: vec!["user".into()],
|
||||
});
|
||||
let entries = all_integrations();
|
||||
let tg = entries.iter().find(|e| e.name == "Telegram").unwrap();
|
||||
assert!(matches!((tg.status_fn)(&config), IntegrationStatus::Active));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telegram_available_when_not_configured() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
let tg = entries.iter().find(|e| e.name == "Telegram").unwrap();
|
||||
assert!(matches!((tg.status_fn)(&config), IntegrationStatus::Available));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imessage_active_when_configured() {
|
||||
let mut config = Config::default();
|
||||
config.channels_config.imessage = Some(IMessageConfig {
|
||||
allowed_contacts: vec!["*".into()],
|
||||
});
|
||||
let entries = all_integrations();
|
||||
let im = entries.iter().find(|e| e.name == "iMessage").unwrap();
|
||||
assert!(matches!((im.status_fn)(&config), IntegrationStatus::Active));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imessage_available_when_not_configured() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
let im = entries.iter().find(|e| e.name == "iMessage").unwrap();
|
||||
assert!(matches!((im.status_fn)(&config), IntegrationStatus::Available));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_active_when_configured() {
|
||||
let mut config = Config::default();
|
||||
config.channels_config.matrix = Some(MatrixConfig {
|
||||
homeserver: "https://m.org".into(),
|
||||
access_token: "tok".into(),
|
||||
room_id: "!r:m".into(),
|
||||
allowed_users: vec![],
|
||||
});
|
||||
let entries = all_integrations();
|
||||
let mx = entries.iter().find(|e| e.name == "Matrix").unwrap();
|
||||
assert!(matches!((mx.status_fn)(&config), IntegrationStatus::Active));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_available_when_not_configured() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
let mx = entries.iter().find(|e| e.name == "Matrix").unwrap();
|
||||
assert!(matches!((mx.status_fn)(&config), IntegrationStatus::Available));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coming_soon_integrations_stay_coming_soon() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
for name in ["WhatsApp", "Signal", "Nostr", "Spotify", "Home Assistant"] {
|
||||
let entry = entries.iter().find(|e| e.name == name).unwrap();
|
||||
assert!(
|
||||
matches!((entry.status_fn)(&config), IntegrationStatus::ComingSoon),
|
||||
"{name} should be ComingSoon"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_and_filesystem_always_active() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
for name in ["Shell", "File System"] {
|
||||
let entry = entries.iter().find(|e| e.name == name).unwrap();
|
||||
assert!(
|
||||
matches!((entry.status_fn)(&config), IntegrationStatus::Active),
|
||||
"{name} should always be Active"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_active_on_macos() {
|
||||
let config = Config::default();
|
||||
let entries = all_integrations();
|
||||
let macos = entries.iter().find(|e| e.name == "macOS").unwrap();
|
||||
let status = (macos.status_fn)(&config);
|
||||
if cfg!(target_os = "macos") {
|
||||
assert!(matches!(status, IntegrationStatus::Active));
|
||||
} else {
|
||||
assert!(matches!(status, IntegrationStatus::Available));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_counts_reasonable() {
|
||||
let entries = all_integrations();
|
||||
let chat_count = entries.iter().filter(|e| e.category == IntegrationCategory::Chat).count();
|
||||
let ai_count = entries.iter().filter(|e| e.category == IntegrationCategory::AiModel).count();
|
||||
assert!(chat_count >= 5, "Expected 5+ chat integrations, got {chat_count}");
|
||||
assert!(ai_count >= 5, "Expected 5+ AI model integrations, got {ai_count}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue