Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
954c75c
feat(stripe-projects): rename ServiceDetail.provider to match 0.40.0 …
cursoragent Sep 15, 2026
5fd19d4
chore(stripe-projects): bless plugin 0.40.0 fixtures
cursoragent Sep 15, 2026
2de23e7
chore(stripe-projects): re-bless catalog with Churnkey/retention
cursoragent Sep 16, 2026
7ee7b97
feat(integrations): add churnkey/retention CatalogResource
cursoragent Sep 16, 2026
d75c25a
refactor(integrations): deslop churnkey module imports
cursoragent Sep 16, 2026
1c3e967
docs(providers): list churnkey, herenow, and perplexity
cursoragent Sep 16, 2026
96927ff
docs(providers): add missing perplexity row
cursoragent Sep 16, 2026
c44be63
chore(stripe-projects): re-bless catalog with Athena/agents
cursoragent Sep 17, 2026
135d156
feat(integrations): add athena/agents CatalogResource
cursoragent Sep 17, 2026
0485ee9
chore(integrations): protect athena/chatbase from catalog generator r…
cursoragent Sep 17, 2026
f4d82f0
refactor(integrations): deslop athena comments and unused reexports
cursoragent Sep 17, 2026
050f4c0
chore(stripe-projects): bless plugin 0.41.0 fixtures
cursoragent Sep 18, 2026
5a5b945
chore(stripe-projects): pin STRIPE_PROJECTS_PINNED to 0.41.0
cursoragent Sep 18, 2026
0a1b9c7
chore(stripe-projects): record four new catalog services
cursoragent Sep 23, 2026
944c7b7
feat(integrations): register resend/email
cursoragent Sep 23, 2026
1a2f5d3
chore(stripe-projects): bless plugin 0.42.0 fixtures
cursoragent Sep 25, 2026
4e8a785
chore(stripe-projects): pin STRIPE_PROJECTS_PINNED to 0.42.0
cursoragent Sep 25, 2026
6ddc722
refactor(stripe-projects): drop surface narration comments
cursoragent Sep 25, 2026
bfc791c
chore(stripe-projects): bless pydantic organization descriptions
cursoragent Sep 26, 2026
761677e
chore(stripe-projects): bless plugin 0.45.0 fixtures
cursoragent Sep 29, 2026
aafeddf
feat(integrations): register quo/app
cursoragent Sep 29, 2026
2f493fa
chore(stripe-projects): pin STRIPE_PROJECTS_PINNED to 0.45.0
cursoragent Sep 29, 2026
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
6 changes: 6 additions & 0 deletions PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ Stripe Projects catalog reference. Offline catalog detail:
| `agentmail` | `agentmail/api` |
| `agentphone` | `agentphone/number` |
| `amplitude` | `amplitude/analytics` |
| `athena` | `athena/agents` |
| `auth0` | `auth0/client` |
| `base44` | `base44_projects/app` |
| `blaxel-sandbox` | `blaxel/sandbox` |
| `browserbase` | `browserbase/project` |
| `chatbase` | `chatbase/agent` |
| `churnkey` | `churnkey/retention` |
| `clerk` | `clerk/auth` |
| `clickhouse` | `clickhouse/clickhouse` |
| `clickhouse-postgres` | `clickhouse/postgres` |
Expand All @@ -73,6 +75,7 @@ Stripe Projects catalog reference. Offline catalog detail:
| `flyio-mpg` | `flyio/mpg` |
| `flyio-sprite` | `flyio/sprite` |
| `gitlab` | `gitlab/project` |
| `herenow` | `herenow/hosting` |
| `inngest` | `inngest/app` |
| `kernel` | `kernel/project` |
| `laravel-cloud` | `laravel_cloud/application` |
Expand All @@ -83,18 +86,21 @@ Stripe Projects catalog reference. Offline catalog detail:
| `neon` | `neon/postgres` |
| `openrouter` | `openrouter/api` |
| `parallel` | `parallel/api` |
| `perplexity` | `perplexity/api` |
| `planetscale-mysql` | `planetscale/mysql` |
| `planetscale-postgresql` | `planetscale/postgresql` |
| `postalform` | `postalform/mail` |
| `posthog` | `posthog/analytics` |
| `prisma` | `prisma/database` |
| `pydantic` | `pydantic/logfire` |
| `quo-app` | `quo/app` |
| `railway-bucket` | `railway/bucket` |
| `railway-hosting` | `railway/hosting` |
| `railway-mongo` | `railway/mongo` |
| `railway-postgres` | `railway/postgres` |
| `railway-redis` | `railway/redis` |
| `render-postgres` | `render/postgres` |
| `resend` | `resend/email` |
| `revenuecat` | `revenuecat/app` |
| `runloop` | `runloop/sandbox` |
| `schematic` | `schematic/schematic-environment` |
Expand Down
4 changes: 4 additions & 0 deletions crates/stackless-integrations/provisional-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
# Externally blocked integrations keep source on disk but omit `pub mod`
# + registry row and live in EXCL — see docs/ADDING-A-PROVIDER.md
# § External pin blockers. Those files are not scanned here.
athena/agents
churnkey/retention
herenow/hosting
perplexity/api
quo/app
resend/email
shopify/store
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ mod tests {
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_api","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_agentmail","provider_name":"AgentMail","service_id":"api","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"type":"object","required":[],"additionalProperties":false,"properties":{}}}]}}"##;
const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_api","object":"v2.provisioning.provider_service_detail","provider":"prvdr_agentmail","provider_name":"AgentMail","service_id":"api","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"type":"object","required":[],"additionalProperties":false,"properties":{}}}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ mod tests {
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_number","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_agentphone","provider_name":"AgentPhone","service_id":"number","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"paid"},"configuration_schema":{"properties":{"agent_name":{"description":"Name for the AI agent","type":"string"},"area_code":{"description":"Preferred 3-digit area code (optional)","type":"string"},"country":{"description":"Country for the phone number","enum":["US","CA"],"type":"string"}},"required":["agent_name"],"type":"object"}}]}}"##;
const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_number","object":"v2.provisioning.provider_service_detail","provider":"prvdr_agentphone","provider_name":"AgentPhone","service_id":"number","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"paid"},"configuration_schema":{"properties":{"agent_name":{"description":"Name for the AI agent","type":"string"},"area_code":{"description":"Preferred 3-digit area code (optional)","type":"string"},"country":{"description":"Country for the phone number","enum":["US","CA"],"type":"string"}},"required":["agent_name"],"type":"object"}}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ mod tests {
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_application","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_algolia","provider_name":"Algolia","service_id":"application","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"paid","paid_pricing":[{"type":"free","configuration":{"accept_terms":"test-accept_terms","plan":"free"}}]},"configuration_schema":{"properties":{"accept_terms":{"type":"string"},"name":{"description":"Name of your Algolia application","type":"string"},"region":{"description":"Where your Algolia application will be created. This cannot be changed after provisioning.","enum":["EU West","US Central","US East","US West","United Kingdom"],"type":"string"}},"required":["name","region","accept_terms"],"type":"object"}}]}}"##;
const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_application","object":"v2.provisioning.provider_service_detail","provider":"prvdr_algolia","provider_name":"Algolia","service_id":"application","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"paid","paid_pricing":[{"type":"free","configuration":{"accept_terms":"test-accept_terms","plan":"free"}}]},"configuration_schema":{"properties":{"accept_terms":{"type":"string"},"name":{"description":"Name of your Algolia application","type":"string"},"region":{"description":"Where your Algolia application will be created. This cannot be changed after provisioning.","enum":["EU West","US Central","US East","US West","United Kingdom"],"type":"string"}},"required":["name","region","accept_terms"],"type":"object"}}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mod tests {
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_analytics","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_amplitude","provider_name":"Amplitude","service_id":"analytics","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"type":"object","required":[],"additionalProperties":false,"properties":{}}}]}}"##;
const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_analytics","object":"v2.provisioning.provider_service_detail","provider":"prvdr_amplitude","provider_name":"Amplitude","service_id":"analytics","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"type":"object","required":[],"additionalProperties":false,"properties":{}}}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
Expand Down
227 changes: 227 additions & 0 deletions crates/stackless-integrations/src/providers/athena/agents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
use std::collections::BTreeMap;

use serde::Serialize;
use stackless_stripe_projects::catalog::verify::CatalogService;
use stackless_stripe_projects::provision::ProvisionContext;

use super::FamilyResource;
use crate::error::IntegrationError;
use crate::hostable::{ConfigScope, Hostable, IntegrationHosting};
use crate::registry;

pub const RESOURCE_KIND: &str = "integration-athena";

#[derive(Debug, Serialize)]
pub struct AthenaAgentsConfig {
pub name: String,
pub instructions: String,
pub connect_mcp: String,
pub enable_image_generation: String,
pub enable_music_generation: String,
pub enable_live: String,
pub enable_coding: String,
pub enable_deep_research: String,
pub enable_google_maps: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcp_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub google_maps_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starter_1: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starter_2: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starter_3: Option<String>,
}

impl CatalogService for AthenaAgentsConfig {
const REFERENCE: &'static str = "athena/agents";
}

#[derive(Debug)]
pub struct AthenaAgents;

impl Hostable for AthenaAgents {
const PROVIDER: &'static str = "athena";
const HOSTING: IntegrationHosting = IntegrationHosting::Managed;
const CONFIG_SCOPE: ConfigScope = ConfigScope::GlobalOnly;
const RESOURCE_KIND: &'static str = RESOURCE_KIND;
const OUTPUTS: &'static [&'static str] = &["agent_id", "api_key", "api_url"];
}

impl FamilyResource for AthenaAgents {
type Config = AthenaAgentsConfig;
const PROVIDER_PREFIX: &'static str = "ATHENA";
// Provisional until pinned by `mise run discover athena/agents`.
const OUTPUT_FIELDS: &'static [(&'static str, &'static str, bool)] = &[
("AGENT_ID", "agent_id", true),
("API_KEY", "api_key", true),
("API_URL", "api_url", true),
];

fn build_config(ctx: &ProvisionContext<'_>) -> Result<AthenaAgentsConfig, IntegrationError> {
let config = super::integration_config(ctx)?;
Ok(AthenaAgentsConfig {
name: super::interp_required(ctx, &config, "name")?,
instructions: super::interp_required(ctx, &config, "instructions")?,
connect_mcp: super::interp_required(ctx, &config, "connect_mcp")?,
enable_image_generation: super::interp_required(
ctx,
&config,
"enable_image_generation",
)?,
enable_music_generation: super::interp_required(
ctx,
&config,
"enable_music_generation",
)?,
enable_live: super::interp_required(ctx, &config, "enable_live")?,
enable_coding: super::interp_required(ctx, &config, "enable_coding")?,
enable_deep_research: super::interp_required(ctx, &config, "enable_deep_research")?,
enable_google_maps: super::interp_required(ctx, &config, "enable_google_maps")?,
mcp_url: super::interp_optional(ctx, &config, "mcp_url")?,
google_maps_api_key: super::interp_optional(ctx, &config, "google_maps_api_key")?,
starter_1: super::interp_optional(ctx, &config, "starter_1")?,
starter_2: super::interp_optional(ctx, &config, "starter_2")?,
starter_3: super::interp_optional(ctx, &config, "starter_3")?,
})
}
}

const REQUIRED_CONFIG_KEYS: &[&str] = &[
"name",
"instructions",
"connect_mcp",
"enable_image_generation",
"enable_music_generation",
"enable_live",
"enable_coding",
"enable_deep_research",
"enable_google_maps",
];

pub fn validate_config(
name: &str,
config: &BTreeMap<String, toml::Value>,
) -> Result<(), IntegrationError> {
for key in REQUIRED_CONFIG_KEYS {
registry::config_string(config, key).map_err(|err| IntegrationError::ConfigInvalid {
location: format!("integrations.{name}.{key}"),
detail: err.to_string(),
})?;
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::ProviderOps;
use crate::resource::ResourcePayload;
use stackless_core::def::StackDef;
use stackless_stripe_projects::stripe::StripeProjects;
use stackless_stripe_projects::test_support;

fn sample_config() -> AthenaAgentsConfig {
AthenaAgentsConfig {
name: "test-name".into(),
instructions: "test-instructions".into(),
connect_mcp: "no".into(),
enable_image_generation: "no".into(),
enable_music_generation: "no".into(),
enable_live: "no".into(),
enable_coding: "no".into(),
enable_deep_research: "no".into(),
enable_google_maps: "no".into(),
mcp_url: None,
google_maps_api_key: None,
starter_1: None,
starter_2: None,
starter_3: None,
}
}

#[test]
fn config_matches_catalog() {
const FIXTURE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../stackless-stripe-projects/tests/fixtures/catalog.json"
));
let catalog = stackless_stripe_projects::Catalog::from_json_envelope(FIXTURE).unwrap();
let failures = stackless_stripe_projects::verify_service(&catalog, &sample_config());
assert!(
failures.is_empty(),
"athena/agents catalog gaps:\n{}",
failures.join("\n")
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"allowed_updates":[],"availability":"available","categories":["ai"],"configuration_schema":{"properties":{"connect_mcp":{"default":"no","description":"Connect one public HTTPS MCP server?","enum":["no","yes"],"type":"string"},"enable_coding":{"default":"no","description":"Enable coding and deep thinking?","enum":["no","yes"],"type":"string"},"enable_deep_research":{"default":"no","description":"Enable deep research?","enum":["no","yes"],"type":"string"},"enable_google_maps":{"default":"no","description":"Enable Google Maps?","enum":["no","yes"],"type":"string"},"enable_image_generation":{"default":"no","description":"Enable image generation?","enum":["no","yes"],"type":"string"},"enable_live":{"default":"no","description":"Enable live voice sessions?","enum":["no","yes"],"type":"string"},"enable_music_generation":{"default":"no","description":"Enable music generation?","enum":["no","yes"],"type":"string"},"google_maps_api_key":{"description":"Google Maps API key. Required only when Google Maps is enabled.","type":"string"},"instructions":{"description":"Instructions for the Athena agent (maximum 2,000 characters).","type":"string"},"mcp_url":{"description":"Public HTTPS MCP server URL. Leave blank when no MCP server is connected.","type":"string"},"name":{"description":"Name for the Athena agent.","type":"string"},"starter_1":{"description":"Optional first conversation starter shown on the public agent.","type":"string"},"starter_2":{"description":"Optional second conversation starter shown on the public agent.","type":"string"},"starter_3":{"description":"Optional third conversation starter shown on the public agent.","type":"string"}},"required":["name","instructions","connect_mcp","enable_image_generation","enable_music_generation","enable_live","enable_coding","enable_deep_research","enable_google_maps"],"type":"object"},"constraints":[],"created":null,"description":"Configurable Athena AI agent with an optional public HTTPS MCP server.","development":false,"group":null,"id":"prvsvc_61VNv58mq1zOThOYP5VZA","kind":"deployable","livemode":true,"llm_context":null,"object":"v2.provisioning.provider_service_detail","pricing":{"component":null,"paid":null,"paid_pricing":[],"type":"free"},"provider":"prvdr_61VNueP9cELmCU01Z5DFA","provider_configuration_schema":{},"provider_name":"Athena","scope":"project","service_id":"agents","updateable_to":["agents"]}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
r#"
[stack]
name = "atto"
[stack.projects.stripe]
project = "project_1"
[integrations.res]
provider = "athena"
name = "demo-name"
instructions = "demo-instructions"
connect_mcp = "no"
enable_image_generation = "no"
enable_music_generation = "no"
enable_live = "no"
enable_coding = "no"
enable_deep_research = "no"
enable_google_maps = "no"
[services.api]
source = { repo = "r", ref = "main" }
env = { OUT = "${integrations.res.api_key}" }
health = { path = "/health" }
[services.api.local]
run = "true"
"#,
)
.unwrap()
}

#[tokio::test]
async fn provision_records_outputs() {
let runner = test_support::provision_script(
CATALOG_ENVELOPE,
serde_json::json!({
"ATHENA_AGENT_ID": "val_agent_id",
"ATHENA_API_KEY": "val_api_key",
"ATHENA_API_URL": "val_api_url"
}),
0,
);
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("stackless.toml"),
"[stack]\nname=\"atto\"\n",
)
.unwrap();
let stripe = StripeProjects::new(&runner, dir.path());

let resource = AthenaAgents
.provision(
&stripe.as_dyn(),
&test_def(),
dir.path(),
"demo",
"res",
"local",
false,
)
.await
.unwrap();
assert_eq!(resource.resource_kind, "integration-athena");
let payload: ResourcePayload = serde_json::from_str(&resource.payload).unwrap();
assert_eq!(payload.outputs["agent_id"], "val_agent_id");
assert_eq!(payload.outputs["api_key"], "val_api_key");
assert_eq!(payload.outputs["api_url"], "val_api_url");
}
}
5 changes: 5 additions & 0 deletions crates/stackless-integrations/src/providers/athena/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod agents;

pub(crate) use crate::resource::{
CatalogResource as FamilyResource, integration_config, interp_optional, interp_required,
};
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ mod tests {
);
}

const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_client","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_auth0","provider_name":"Auth0","service_id":"client","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"properties":{"name":{"minLength":1,"type":"string"}},"required":["name"],"type":"object"}}]}}"##;
const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_client","object":"v2.provisioning.provider_service_detail","provider":"prvdr_auth0","provider_name":"Auth0","service_id":"client","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"component"},"configuration_schema":{"properties":{"name":{"minLength":1,"type":"string"}},"required":["name"],"type":"object"}}]}}"##;

fn test_def() -> StackDef {
StackDef::parse(
Expand Down
Loading
Loading