From aeba13d2aaaefd41602c8cc1a89be5e8f2032a94 Mon Sep 17 00:00:00 2001 From: Shinu Cherian Date: Fri, 24 Jul 2026 00:18:46 +0530 Subject: [PATCH 1/3] feat(integrations): Add Trello setup wizard and create_card tool (#4216) --- .env.example | 7 +- README.md | 2 +- docs/integrations/trello.mdx | 6 +- integrations/_catalog_impl.py | 21 ++++ integrations/registry.py | 8 +- integrations/trello/setup.py | 43 ++++++++ integrations/trello/tools/__init__.py | 7 ++ integrations/trello/tools/create_card.py | 104 ++++++++++++++++++ integrations/trello/verifier.py | 45 ++++---- .../cli/wizard/_integration_configurators.py | 3 + .../cli/wizard/configurators/productivity.py | 14 +++ surfaces/cli/wizard/onboard_integrations.py | 6 + 12 files changed, 236 insertions(+), 30 deletions(-) create mode 100644 integrations/trello/setup.py create mode 100644 integrations/trello/tools/__init__.py create mode 100644 integrations/trello/tools/create_card.py diff --git a/.env.example b/.env.example index aa55a66142..8ee0ed1974 100644 --- a/.env.example +++ b/.env.example @@ -522,7 +522,12 @@ VICTORIA_LOGS_URL= VICTORIA_LOGS_TENANT_ID= # Vercel -VERCEL_API_TOKEN= +# TRELLO_API_KEY= +# TRELLO_TOKEN= +# TRELLO_BOARD_ID= +# TRELLO_LIST_ID= + +# VERCEL_API_TOKEN= VERCEL_TEAM_ID= # PostHog (REST credentials) diff --git a/README.md b/README.md index a4d0717abd..8158f5321d 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ OpenSRE connects to 60+ tools and services across the modern cloud stack, from L | **Database** | MongoDB · ClickHouse · PostgreSQL · MySQL · MariaDB · MongoDB Atlas · Azure SQL · Snowflake · Redis · RDS · Supabase | | | **Data Platform** | Apache Airflow · Apache Kafka · Apache Spark · Prefect · RabbitMQ · Dagster | | | **Dev Tools** | GitHub · GitHub MCP · Bitbucket · GitLab | | -| **Incident Management** | PagerDuty · Opsgenie · Jira · Alertmanager · incident.io · ServiceNow | [Trello](https://github.com/Tracer-Cloud/opensre/issues/361) · [Linear](https://github.com/Tracer-Cloud/opensre/issues/124) | +| **Incident Management** | PagerDuty · Opsgenie · Jira · Alertmanager · incident.io · ServiceNow · Trello | [Linear](https://github.com/Tracer-Cloud/opensre/issues/124) | | **Communication** | Slack · Google Docs · Discord · Telegram · Rocket.Chat · WhatsApp | [Notion](https://github.com/Tracer-Cloud/opensre/issues/286) · [Teams](https://github.com/Tracer-Cloud/opensre/issues/138) · [Confluence](https://github.com/Tracer-Cloud/opensre/issues/313) | | **Agent Deployment** | Vercel · EC2 · ECS · Railway | | | **Protocols** | MCP · ACP · OpenClaw | | diff --git a/docs/integrations/trello.mdx b/docs/integrations/trello.mdx index 3dad3aa009..ce61dab4f0 100644 --- a/docs/integrations/trello.mdx +++ b/docs/integrations/trello.mdx @@ -66,7 +66,7 @@ You can also save your Trello configuration to `~/.opensre/integrations.json`: ## Getting your API key and token -1. Visit https://trello.com/app-key +1. Visit https://trello.com/power-ups/admin 2. Copy your Trello API key 3. Generate a token from the same page 4. Authorize access when prompted @@ -111,7 +111,3 @@ Once configured, OpenSRE can: - Track follow-up tasks on your incident board Set `TRELLO_LIST_ID` to the list where new cards should appear. If omitted, card creation requires the list ID at investigation time. - - -Trello does not currently support `opensre integrations verify trello`. Confirm your credentials during interactive setup or by creating a test card from an investigation. - diff --git a/integrations/_catalog_impl.py b/integrations/_catalog_impl.py index 64f136691f..320f792b6f 100644 --- a/integrations/_catalog_impl.py +++ b/integrations/_catalog_impl.py @@ -212,6 +212,7 @@ from integrations.tempo import classify as _classify_tempo from integrations.tempo import tempo_config_from_env from integrations.temporal import classify as _classify_temporal +from integrations.trello.config import trello_config_from_env from integrations.temporal.client import TemporalConfig from integrations.twilio import classify as _classify_twilio from integrations.vercel import classify as _classify_vercel @@ -998,6 +999,26 @@ def load_env_integrations() -> list[dict[str, Any]]: discord_config = DiscordBotConfig.model_validate( { "bot_token": discord_bot_token, + } + ) + except Exception as exc: + _report_env_loader_failure(exc, integration="discord") + else: + integrations.append( + _active_env_record( + "discord", + discord_config.model_dump(exclude={"integration_id"}), + ) + ) + + trello_config = trello_config_from_env() + if trello_config: + integrations.append( + _active_env_record( + "trello", + trello_config.model_dump(exclude={"integration_id"}), + ) + ) "application_id": os.getenv("DISCORD_APPLICATION_ID", "").strip(), "public_key": os.getenv("DISCORD_PUBLIC_KEY", "").strip(), "default_channel_id": os.getenv("DISCORD_DEFAULT_CHANNEL_ID", "").strip() diff --git a/integrations/registry.py b/integrations/registry.py index 9cd5ba8103..d2c09ca34a 100644 --- a/integrations/registry.py +++ b/integrations/registry.py @@ -384,7 +384,13 @@ class IntegrationSpec: setup_order=40, verify_order=54, ), - IntegrationSpec(service="trello"), + IntegrationSpec( + service="trello", + has_verifier=True, + direct_effective=True, + setup_order=43, + verify_order=57, + ), IntegrationSpec(service="rds", setup_order=11), IntegrationSpec( service="supabase", diff --git a/integrations/trello/setup.py b/integrations/trello/setup.py new file mode 100644 index 0000000000..097733d904 --- /dev/null +++ b/integrations/trello/setup.py @@ -0,0 +1,43 @@ +"""What Trello needs before it is considered configured.""" + +from __future__ import annotations + +from integrations.setup_flow import IntegrationSetupSpec, SetupField +from integrations.trello.verifier import verify_trello + +TRELLO_SETUP = IntegrationSetupSpec( + service="trello", + fields=( + SetupField( + name="api_key", + label="API Key", + prompt="Trello API Key", + env_var="TRELLO_API_KEY", + secret=True, + ), + SetupField( + name="token", + label="Token", + prompt="Trello Token", + env_var="TRELLO_TOKEN", + secret=True, + ), + SetupField( + name="board_id", + label="Board ID", + prompt="Trello Board ID (optional)", + env_var="TRELLO_BOARD_ID", + optional=True, + ), + SetupField( + name="list_id", + label="List ID", + prompt="Trello List ID (optional)", + env_var="TRELLO_LIST_ID", + optional=True, + ), + ), + verify=verify_trello, +) + +__all__ = ["TRELLO_SETUP"] diff --git a/integrations/trello/tools/__init__.py b/integrations/trello/tools/__init__.py new file mode 100644 index 0000000000..a617a28f9c --- /dev/null +++ b/integrations/trello/tools/__init__.py @@ -0,0 +1,7 @@ +"""Trello integration tools.""" + +from __future__ import annotations + +from integrations.trello.tools.create_card import create_trello_card_tool + +__all__ = ["create_trello_card_tool"] diff --git a/integrations/trello/tools/create_card.py b/integrations/trello/tools/create_card.py new file mode 100644 index 0000000000..5a7cb296e9 --- /dev/null +++ b/integrations/trello/tools/create_card.py @@ -0,0 +1,104 @@ +"""Trello create card tool for investigation workflows.""" + +from __future__ import annotations + +from typing import Any + +from core.tool_framework.base import BaseTool +from core.tool_framework.utils.tool_availability import tool_unavailable +from integrations.trello.client import create_trello_card +from integrations.trello.config import build_trello_config + + +class CreateTrelloCardTool(BaseTool): + """Create a new card in Trello for tracking tasks or incidents.""" + + name = "create_trello_card" + source = "trello" + description = ( + "Create a new card in Trello to track incident resolution, action items, " + "or postmortem tasks. Requires the name and description of the card." + ) + use_cases = [ + "Creating a tracking card for a newly discovered bug", + "Adding action items from an incident postmortem", + "Tracking remediation steps during an active incident", + ] + requires = ["api_key", "token", "name", "desc"] + injected_params = ["api_key", "token", "board_id", "list_id"] + input_schema = { + "type": "object", + "properties": { + "api_key": {"type": "string", "description": "Trello API key"}, + "token": {"type": "string", "description": "Trello token"}, + "board_id": {"type": "string", "description": "Trello Board ID (optional)"}, + "list_id": {"type": "string", "description": "Trello List ID"}, + "name": {"type": "string", "description": "Title of the card"}, + "desc": {"type": "string", "description": "Detailed description for the card"}, + }, + "required": ["api_key", "token", "name", "desc"], + } + outputs = { + "id": "The ID of the created card", + "url": "The URL of the created card", + } + + def is_available(self, sources: dict) -> bool: + trello_config = sources.get("trello", {}) + return bool(trello_config.get("api_key") and trello_config.get("token")) + + def extract_params(self, sources: dict) -> dict[str, Any]: + trello = sources.get("trello", {}) + return { + "api_key": trello.get("api_key", ""), + "token": trello.get("token", ""), + "board_id": trello.get("board_id", ""), + "list_id": trello.get("list_id", ""), + "name": "", + "desc": "", + } + + def run( + self, + api_key: str, + token: str, + name: str, + desc: str, + board_id: str = "", + list_id: str = "", + **_kwargs: Any, + ) -> dict[str, Any]: + if not api_key or not token: + return tool_unavailable("trello", "Trello integration is not configured.") + if not name: + return tool_unavailable("trello", "Card name is required.") + if not desc: + return tool_unavailable("trello", "Card description is required.") + + config = build_trello_config( + { + "api_key": api_key, + "token": token, + "board_id": board_id, + "list_id": list_id, + } + ) + + try: + result = create_trello_card( + config=config, + name=name, + desc=desc, + list_id=list_id if list_id else None, + ) + return { + "source": "trello", + "available": True, + "card_id": result.get("id", ""), + "url": result.get("shortUrl", ""), + } + except Exception as err: + return tool_unavailable("trello", str(err)) + + +create_trello_card_tool = CreateTrelloCardTool() diff --git a/integrations/trello/verifier.py b/integrations/trello/verifier.py index b708370440..0443cc7c02 100644 --- a/integrations/trello/verifier.py +++ b/integrations/trello/verifier.py @@ -1,47 +1,48 @@ """Trello credential and connectivity verification.""" import logging -from dataclasses import dataclass +from typing import Any import httpx import integrations.trello.client as client from integrations._validation_helpers import report_validation_failure -from integrations.trello.config import TrelloConfig +from integrations.trello.config import build_trello_config +from integrations.verification import register_verifier, result logger = logging.getLogger(__name__) -@dataclass(frozen=True) -class TrelloValidationResult: - """Result of validating a Trello integration.""" - - ok: bool - detail: str - +@register_verifier("trello") +def verify_trello(source: str, config: dict[str, Any]) -> dict[str, str]: + """Validate Trello connectivity via the standard registry.""" + try: + trello_config = build_trello_config(config) + except Exception as err: + return result("trello", source, "missing", f"Invalid Trello configuration: {err}") -def validate_trello_config(config: TrelloConfig) -> TrelloValidationResult: - """Validate Trello connectivity.""" - if not config.api_key: - return TrelloValidationResult(ok=False, detail="Trello API key is required.") - if not config.token: - return TrelloValidationResult(ok=False, detail="Trello token is required.") + if not trello_config.api_key: + return result("trello", source, "missing", "Trello API key is required.") + if not trello_config.token: + return result("trello", source, "missing", "Trello token is required.") try: - member = client.validate_trello_connection(config=config) + member = client.validate_trello_connection(config=trello_config) username = member.get("username", "unknown") - return TrelloValidationResult( - ok=True, - detail=f"Trello connectivity successful. Authenticated as @{username}", + return result( + "trello", + source, + "passed", + f"Trello connectivity successful. Authenticated as @{username}", ) except httpx.HTTPStatusError as err: detail = err.response.text.strip() or str(err) - return TrelloValidationResult(ok=False, detail=f"Trello validation failed: {detail}") + return result("trello", source, "failed", f"Trello validation failed: {detail}") except Exception as err: report_validation_failure( err, logger=logger, integration="trello", - method="validate_trello_config", + method="verify_trello", ) - return TrelloValidationResult(ok=False, detail=f"Trello validation failed: {err}") + return result("trello", source, "error", f"Trello validation failed: {err}") diff --git a/surfaces/cli/wizard/_integration_configurators.py b/surfaces/cli/wizard/_integration_configurators.py index 8a8a11cd69..de37b8f74b 100644 --- a/surfaces/cli/wizard/_integration_configurators.py +++ b/surfaces/cli/wizard/_integration_configurators.py @@ -43,6 +43,7 @@ _configure_jira, _configure_notion, _configure_servicenow, + _configure_trello, ) from surfaces.cli.wizard.configurators.sentry import _configure_sentry, _configure_sentry_mcp from surfaces.cli.wizard.configurators.vercel import _configure_vercel @@ -98,6 +99,7 @@ def _configure_selected_integrations() -> tuple[list[str], str | None]: "betterstack": _configure_betterstack, "jira": _configure_jira, "servicenow": _configure_servicenow, + "trello": _configure_trello, "alertmanager": _configure_alertmanager, "opsgenie": _configure_opsgenie, "pagerduty": _configure_pagerduty, @@ -131,6 +133,7 @@ def _configure_selected_integrations() -> tuple[list[str], str | None]: "dagster": "dagster", "jira": "jira", "servicenow": "servicenow", + "trello": "trello", "alertmanager": "alertmanager", "opsgenie": "opsgenie", "pagerduty": "pagerduty", diff --git a/surfaces/cli/wizard/configurators/productivity.py b/surfaces/cli/wizard/configurators/productivity.py index 835dab1665..37d7556bf3 100644 --- a/surfaces/cli/wizard/configurators/productivity.py +++ b/surfaces/cli/wizard/configurators/productivity.py @@ -95,6 +95,20 @@ def _configure_servicenow() -> tuple[str, str]: ) +def _configure_trello() -> tuple[str, str]: + from integrations.trello.setup import TRELLO_SETUP + + return configure_from_spec( + TRELLO_SETUP, + title="Trello", + intro=( + "\n[bold]Trello Integration[/bold]\n" + "Create a Power-Up and generate an API key and token at " + "https://trello.com/power-ups/admin\n" + ), + ) + + def _configure_google_docs() -> tuple[str, str]: _, credentials = _integration_defaults("google_docs") while True: diff --git a/surfaces/cli/wizard/onboard_integrations.py b/surfaces/cli/wizard/onboard_integrations.py index 0aa898bb1f..00796d8297 100644 --- a/surfaces/cli/wizard/onboard_integrations.py +++ b/surfaces/cli/wizard/onboard_integrations.py @@ -157,6 +157,12 @@ group="Incident & Comms", hint="Post findings to a Rocket.Chat channel", ), + Choice( + value="trello", + label="Trello", + group="Incident & Comms", + hint="Manage tasks and cards in Trello", + ), Choice( value="google_docs", label="Google Docs", From a482d62312f7d98f21c4cdb420bf57f8cc2ba143 Mon Sep 17 00:00:00 2001 From: Shinu Cherian Date: Fri, 24 Jul 2026 00:28:29 +0530 Subject: [PATCH 2/3] fix: resolve greptile code review issues --- integrations/_catalog_impl.py | 21 +++++---------------- integrations/trello/__init__.py | 6 ++---- tools/registry_discovery.py | 1 + 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/integrations/_catalog_impl.py b/integrations/_catalog_impl.py index 320f792b6f..40ebaac4df 100644 --- a/integrations/_catalog_impl.py +++ b/integrations/_catalog_impl.py @@ -999,17 +999,16 @@ def load_env_integrations() -> list[dict[str, Any]]: discord_config = DiscordBotConfig.model_validate( { "bot_token": discord_bot_token, + "application_id": os.getenv("DISCORD_APPLICATION_ID", "").strip(), + "public_key": os.getenv("DISCORD_PUBLIC_KEY", "").strip(), + "default_channel_id": os.getenv("DISCORD_DEFAULT_CHANNEL_ID", "").strip() + or None, } ) except Exception as exc: _report_env_loader_failure(exc, integration="discord") else: - integrations.append( - _active_env_record( - "discord", - discord_config.model_dump(exclude={"integration_id"}), - ) - ) + integrations.append(_active_env_record("discord", discord_config.model_dump())) trello_config = trello_config_from_env() if trello_config: @@ -1019,16 +1018,6 @@ def load_env_integrations() -> list[dict[str, Any]]: trello_config.model_dump(exclude={"integration_id"}), ) ) - "application_id": os.getenv("DISCORD_APPLICATION_ID", "").strip(), - "public_key": os.getenv("DISCORD_PUBLIC_KEY", "").strip(), - "default_channel_id": os.getenv("DISCORD_DEFAULT_CHANNEL_ID", "").strip() - or None, - } - ) - except Exception as exc: - _report_env_loader_failure(exc, integration="discord") - else: - integrations.append(_active_env_record("discord", discord_config.model_dump())) airflow_config = airflow_config_from_env() if airflow_config is not None: diff --git a/integrations/trello/__init__.py b/integrations/trello/__init__.py index fbe6ead041..6c44a2115a 100644 --- a/integrations/trello/__init__.py +++ b/integrations/trello/__init__.py @@ -11,17 +11,15 @@ trello_config_from_env, ) from integrations.trello.verifier import ( - TrelloValidationResult, - validate_trello_config, + verify_trello, ) __all__ = [ "DEFAULT_TRELLO_BASE_URL", "TrelloConfig", - "TrelloValidationResult", "build_trello_config", "create_trello_card", "trello_config_from_env", - "validate_trello_config", "validate_trello_connection", + "verify_trello", ] diff --git a/tools/registry_discovery.py b/tools/registry_discovery.py index f5aca378fd..6c342484e5 100644 --- a/tools/registry_discovery.py +++ b/tools/registry_discovery.py @@ -85,6 +85,7 @@ "integrations.tempo.tools", "integrations.temporal.tools", "integrations.tracer.tools", + "integrations.trello.tools", "integrations.twilio.tools", "integrations.vercel.tools", "integrations.victoria_logs.tools", From 9ebaf2f101806487ed27475b1f2b7e80352bee65 Mon Sep 17 00:00:00 2001 From: Shinu Cherian Date: Fri, 24 Jul 2026 00:40:09 +0530 Subject: [PATCH 3/3] fix(trello): require list_id in setup/tool and restore vercel env variable --- .env.example | 13 +++++++------ integrations/trello/setup.py | 4 ++-- integrations/trello/tools/create_card.py | 23 +++++++++++++++++------ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 8ee0ed1974..7e7f2f0a24 100644 --- a/.env.example +++ b/.env.example @@ -521,13 +521,14 @@ AZURE_MAX_RESULTS=100 VICTORIA_LOGS_URL= VICTORIA_LOGS_TENANT_ID= -# Vercel -# TRELLO_API_KEY= -# TRELLO_TOKEN= -# TRELLO_BOARD_ID= -# TRELLO_LIST_ID= +# Trello +TRELLO_API_KEY= +TRELLO_TOKEN= +TRELLO_BOARD_ID= +TRELLO_LIST_ID= -# VERCEL_API_TOKEN= +# Vercel +VERCEL_API_TOKEN= VERCEL_TEAM_ID= # PostHog (REST credentials) diff --git a/integrations/trello/setup.py b/integrations/trello/setup.py index 097733d904..4e9ae05a82 100644 --- a/integrations/trello/setup.py +++ b/integrations/trello/setup.py @@ -32,9 +32,9 @@ SetupField( name="list_id", label="List ID", - prompt="Trello List ID (optional)", + prompt="Trello List ID", env_var="TRELLO_LIST_ID", - optional=True, + optional=False, ), ), verify=verify_trello, diff --git a/integrations/trello/tools/create_card.py b/integrations/trello/tools/create_card.py index 5a7cb296e9..153711ca1c 100644 --- a/integrations/trello/tools/create_card.py +++ b/integrations/trello/tools/create_card.py @@ -24,28 +24,35 @@ class CreateTrelloCardTool(BaseTool): "Adding action items from an incident postmortem", "Tracking remediation steps during an active incident", ] - requires = ["api_key", "token", "name", "desc"] - injected_params = ["api_key", "token", "board_id", "list_id"] + requires = ["api_key", "token", "list_id", "name", "desc"] + injected_params = ["api_key", "token"] input_schema = { "type": "object", "properties": { "api_key": {"type": "string", "description": "Trello API key"}, "token": {"type": "string", "description": "Trello token"}, "board_id": {"type": "string", "description": "Trello Board ID (optional)"}, - "list_id": {"type": "string", "description": "Trello List ID"}, + "list_id": { + "type": "string", + "description": "Trello List ID (optional if configured in environment)", + }, "name": {"type": "string", "description": "Title of the card"}, "desc": {"type": "string", "description": "Detailed description for the card"}, }, "required": ["api_key", "token", "name", "desc"], } outputs = { - "id": "The ID of the created card", + "card_id": "The ID of the created card", "url": "The URL of the created card", } def is_available(self, sources: dict) -> bool: trello_config = sources.get("trello", {}) - return bool(trello_config.get("api_key") and trello_config.get("token")) + return bool( + trello_config.get("api_key") + and trello_config.get("token") + and trello_config.get("list_id") + ) def extract_params(self, sources: dict) -> dict[str, Any]: trello = sources.get("trello", {}) @@ -84,12 +91,16 @@ def run( } ) + target_list_id = (list_id or config.list_id).strip() + if not target_list_id: + return tool_unavailable("trello", "List ID is required to create a Trello card.") + try: result = create_trello_card( config=config, name=name, desc=desc, - list_id=list_id if list_id else None, + list_id=target_list_id, ) return { "source": "trello",