iflow-mcp-m507_ai-soc-agent 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
- src/__init__.py +8 -0
- src/ai_controller/README.md +139 -0
- src/ai_controller/__init__.py +12 -0
- src/ai_controller/agent_executor.py +596 -0
- src/ai_controller/cli/__init__.py +2 -0
- src/ai_controller/cli/main.py +243 -0
- src/ai_controller/session_manager.py +409 -0
- src/ai_controller/web/__init__.py +2 -0
- src/ai_controller/web/server.py +1181 -0
- src/ai_controller/web/static/css/README.md +102 -0
- src/api/__init__.py +13 -0
- src/api/case_management.py +271 -0
- src/api/edr.py +187 -0
- src/api/kb.py +136 -0
- src/api/siem.py +308 -0
- src/core/__init__.py +10 -0
- src/core/config.py +242 -0
- src/core/config_storage.py +684 -0
- src/core/dto.py +50 -0
- src/core/errors.py +36 -0
- src/core/logging.py +128 -0
- src/integrations/__init__.py +8 -0
- src/integrations/case_management/__init__.py +5 -0
- src/integrations/case_management/iris/__init__.py +11 -0
- src/integrations/case_management/iris/iris_client.py +885 -0
- src/integrations/case_management/iris/iris_http.py +274 -0
- src/integrations/case_management/iris/iris_mapper.py +263 -0
- src/integrations/case_management/iris/iris_models.py +128 -0
- src/integrations/case_management/thehive/__init__.py +8 -0
- src/integrations/case_management/thehive/thehive_client.py +193 -0
- src/integrations/case_management/thehive/thehive_http.py +147 -0
- src/integrations/case_management/thehive/thehive_mapper.py +190 -0
- src/integrations/case_management/thehive/thehive_models.py +125 -0
- src/integrations/cti/__init__.py +6 -0
- src/integrations/cti/local_tip/__init__.py +10 -0
- src/integrations/cti/local_tip/local_tip_client.py +90 -0
- src/integrations/cti/local_tip/local_tip_http.py +110 -0
- src/integrations/cti/opencti/__init__.py +10 -0
- src/integrations/cti/opencti/opencti_client.py +101 -0
- src/integrations/cti/opencti/opencti_http.py +418 -0
- src/integrations/edr/__init__.py +6 -0
- src/integrations/edr/elastic_defend/__init__.py +6 -0
- src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
- src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
- src/integrations/eng/__init__.py +10 -0
- src/integrations/eng/clickup/__init__.py +8 -0
- src/integrations/eng/clickup/clickup_client.py +513 -0
- src/integrations/eng/clickup/clickup_http.py +156 -0
- src/integrations/eng/github/__init__.py +8 -0
- src/integrations/eng/github/github_client.py +169 -0
- src/integrations/eng/github/github_http.py +158 -0
- src/integrations/eng/trello/__init__.py +8 -0
- src/integrations/eng/trello/trello_client.py +207 -0
- src/integrations/eng/trello/trello_http.py +162 -0
- src/integrations/kb/__init__.py +12 -0
- src/integrations/kb/fs_kb_client.py +313 -0
- src/integrations/siem/__init__.py +6 -0
- src/integrations/siem/elastic/__init__.py +6 -0
- src/integrations/siem/elastic/elastic_client.py +3319 -0
- src/integrations/siem/elastic/elastic_http.py +165 -0
- src/mcp/README.md +183 -0
- src/mcp/TOOLS.md +2827 -0
- src/mcp/__init__.py +13 -0
- src/mcp/__main__.py +18 -0
- src/mcp/agent_profiles.py +408 -0
- src/mcp/flow_agent_profiles.py +424 -0
- src/mcp/mcp_server.py +4086 -0
- src/mcp/rules_engine.py +487 -0
- src/mcp/runbook_manager.py +264 -0
- src/orchestrator/__init__.py +11 -0
- src/orchestrator/incident_workflow.py +244 -0
- src/orchestrator/tools_case.py +1085 -0
- src/orchestrator/tools_cti.py +359 -0
- src/orchestrator/tools_edr.py +315 -0
- src/orchestrator/tools_eng.py +378 -0
- src/orchestrator/tools_kb.py +156 -0
- src/orchestrator/tools_siem.py +1709 -0
- src/web/__init__.py +8 -0
- src/web/config_server.py +511 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub client for creating project items and recommendations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
from ....core.config import SamiConfig
|
|
10
|
+
from ....core.errors import IntegrationError
|
|
11
|
+
from ....core.logging import get_logger
|
|
12
|
+
from .github_http import GitHubHttpClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger("sami.integrations.github.client")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GitHubClient:
|
|
19
|
+
"""
|
|
20
|
+
Client for interacting with GitHub Projects API to create project items and recommendations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
http_client: GitHubHttpClient,
|
|
26
|
+
fine_tuning_project_id: str,
|
|
27
|
+
engineering_project_id: str,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Initialize GitHub client.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
http_client: HTTP client for GitHub API
|
|
34
|
+
fine_tuning_project_id: GitHub project ID for fine-tuning recommendations
|
|
35
|
+
engineering_project_id: GitHub project ID for engineering/visibility recommendations
|
|
36
|
+
"""
|
|
37
|
+
self._http = http_client
|
|
38
|
+
self.fine_tuning_project_id = fine_tuning_project_id
|
|
39
|
+
self.engineering_project_id = engineering_project_id
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_config(cls, config: SamiConfig) -> "GitHubClient":
|
|
43
|
+
"""
|
|
44
|
+
Factory to construct a client from ``SamiConfig``.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config: SamiConfig instance with GitHub configuration
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
GitHubClient instance
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
IntegrationError: If GitHub configuration is not set
|
|
54
|
+
"""
|
|
55
|
+
if not config.eng or not config.eng.github:
|
|
56
|
+
raise IntegrationError("GitHub configuration is not set in SamiConfig")
|
|
57
|
+
|
|
58
|
+
github_config = config.eng.github
|
|
59
|
+
http_client = GitHubHttpClient(
|
|
60
|
+
api_token=github_config.api_token,
|
|
61
|
+
timeout_seconds=github_config.timeout_seconds,
|
|
62
|
+
verify_ssl=github_config.verify_ssl,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return cls(
|
|
66
|
+
http_client=http_client,
|
|
67
|
+
fine_tuning_project_id=github_config.fine_tuning_project_id,
|
|
68
|
+
engineering_project_id=github_config.engineering_project_id,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def create_fine_tuning_recommendation(
|
|
72
|
+
self,
|
|
73
|
+
title: str,
|
|
74
|
+
description: str,
|
|
75
|
+
content_id: Optional[str] = None,
|
|
76
|
+
content_type: str = "DraftIssue",
|
|
77
|
+
) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Create a fine-tuning recommendation project item in GitHub.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
title: Item title
|
|
83
|
+
description: Item description/body
|
|
84
|
+
content_id: Optional content ID (for linking to issues/PRs)
|
|
85
|
+
content_type: Content type (DraftIssue, Issue, PullRequest). Default: DraftIssue
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary with project item information
|
|
89
|
+
"""
|
|
90
|
+
logger.info(f"Creating fine-tuning recommendation: {title}")
|
|
91
|
+
|
|
92
|
+
# GitHub Projects API v1 (REST) - Note: v1 is deprecated but still functional
|
|
93
|
+
# We use REST API v1 for simplicity: POST /projects/columns/{column_id}/cards
|
|
94
|
+
|
|
95
|
+
# Get the project columns
|
|
96
|
+
columns = self._http.get(f"/projects/{self.fine_tuning_project_id}/columns")
|
|
97
|
+
|
|
98
|
+
if not columns:
|
|
99
|
+
raise IntegrationError("No columns found in fine-tuning project")
|
|
100
|
+
|
|
101
|
+
# Use the first column
|
|
102
|
+
column_id = columns[0].get("id")
|
|
103
|
+
|
|
104
|
+
# Create a card/item in the column
|
|
105
|
+
# For draft issues, we use note parameter
|
|
106
|
+
card_data = {
|
|
107
|
+
"note": f"## {title}\n\n{description}",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
card = self._http.post(f"/projects/columns/{column_id}/cards", json_data=card_data)
|
|
111
|
+
|
|
112
|
+
logger.info(f"Created fine-tuning recommendation project item: {card.get('id')}")
|
|
113
|
+
return card
|
|
114
|
+
|
|
115
|
+
def create_visibility_recommendation(
|
|
116
|
+
self,
|
|
117
|
+
title: str,
|
|
118
|
+
description: str,
|
|
119
|
+
content_id: Optional[str] = None,
|
|
120
|
+
content_type: str = "DraftIssue",
|
|
121
|
+
) -> Dict[str, Any]:
|
|
122
|
+
"""
|
|
123
|
+
Create a visibility/engineering recommendation project item in GitHub.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
title: Item title
|
|
127
|
+
description: Item description/body
|
|
128
|
+
content_id: Optional content ID (for linking to issues/PRs)
|
|
129
|
+
content_type: Content type (DraftIssue, Issue, PullRequest). Default: DraftIssue
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dictionary with project item information
|
|
133
|
+
"""
|
|
134
|
+
logger.info(f"Creating visibility recommendation: {title}")
|
|
135
|
+
|
|
136
|
+
# Get the project columns
|
|
137
|
+
columns = self._http.get(f"/projects/{self.engineering_project_id}/columns")
|
|
138
|
+
|
|
139
|
+
if not columns:
|
|
140
|
+
raise IntegrationError("No columns found in engineering project")
|
|
141
|
+
|
|
142
|
+
# Use the first column
|
|
143
|
+
column_id = columns[0].get("id")
|
|
144
|
+
|
|
145
|
+
# Create a card/item in the column
|
|
146
|
+
card_data = {
|
|
147
|
+
"note": f"## {title}\n\n{description}",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
card = self._http.post(f"/projects/columns/{column_id}/cards", json_data=card_data)
|
|
151
|
+
|
|
152
|
+
logger.info(f"Created visibility recommendation project item: {card.get('id')}")
|
|
153
|
+
return card
|
|
154
|
+
|
|
155
|
+
def ping(self) -> bool:
|
|
156
|
+
"""
|
|
157
|
+
Check if GitHub API is reachable.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if API is reachable, False otherwise
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
# Try to get authenticated user
|
|
164
|
+
self._http.get("/user")
|
|
165
|
+
return True
|
|
166
|
+
except IntegrationError:
|
|
167
|
+
logger.exception("GitHub ping failed")
|
|
168
|
+
return False
|
|
169
|
+
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Low-level HTTP client for GitHub API.
|
|
3
|
+
|
|
4
|
+
This module is responsible for:
|
|
5
|
+
- authentication (Personal Access Token)
|
|
6
|
+
- building URLs
|
|
7
|
+
- making HTTP requests
|
|
8
|
+
- basic error handling
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any, Dict, Optional
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
from ....core.errors import IntegrationError
|
|
20
|
+
from ....core.logging import get_logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = get_logger("sami.integrations.github.http")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class GitHubHttpClient:
|
|
28
|
+
"""
|
|
29
|
+
Simple HTTP client for GitHub's REST API.
|
|
30
|
+
|
|
31
|
+
GitHub API documentation: https://docs.github.com/en/rest
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
api_token: str
|
|
35
|
+
timeout_seconds: int = 30
|
|
36
|
+
verify_ssl: bool = True
|
|
37
|
+
|
|
38
|
+
def _headers(self) -> Dict[str, str]:
|
|
39
|
+
"""Get authentication headers."""
|
|
40
|
+
return {
|
|
41
|
+
"Authorization": f"Bearer {self.api_token}",
|
|
42
|
+
"Accept": "application/vnd.github+json",
|
|
43
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def _build_url(self, endpoint: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Build a full URL from an endpoint.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
endpoint: API endpoint path (e.g., "/repos/{owner}/{repo}/projects")
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Full URL string
|
|
55
|
+
"""
|
|
56
|
+
base_url = "https://api.github.com"
|
|
57
|
+
endpoint = endpoint.lstrip("/")
|
|
58
|
+
return f"{base_url}/{endpoint}"
|
|
59
|
+
|
|
60
|
+
def _handle_github_error(self, response: requests.Response) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Raise IntegrationError if the response indicates an error.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
response: HTTP response object
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
IntegrationError: If the response indicates an error
|
|
69
|
+
"""
|
|
70
|
+
if response.status_code < 400:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
error_data = response.json()
|
|
75
|
+
message = error_data.get("message", f"HTTP {response.status_code}")
|
|
76
|
+
errors = error_data.get("errors", [])
|
|
77
|
+
if errors:
|
|
78
|
+
error_details = "; ".join([str(e) for e in errors])
|
|
79
|
+
message = f"{message}: {error_details}"
|
|
80
|
+
except Exception:
|
|
81
|
+
message = f"HTTP {response.status_code}: {response.text[:200]}"
|
|
82
|
+
|
|
83
|
+
raise IntegrationError(f"GitHub API error: {message}")
|
|
84
|
+
|
|
85
|
+
def request(
|
|
86
|
+
self,
|
|
87
|
+
method: str,
|
|
88
|
+
endpoint: str,
|
|
89
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
90
|
+
params: Optional[Dict[str, Any]] = None,
|
|
91
|
+
) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Make an HTTP request to GitHub API.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
method: HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
97
|
+
endpoint: API endpoint path
|
|
98
|
+
json_data: JSON payload (for POST, PUT, PATCH)
|
|
99
|
+
params: Query parameters
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Response JSON as dictionary
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
IntegrationError: If the request fails
|
|
106
|
+
"""
|
|
107
|
+
url = self._build_url(endpoint)
|
|
108
|
+
headers = self._headers()
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
logger.debug(f"GitHub {method} {url}")
|
|
112
|
+
if params:
|
|
113
|
+
logger.debug(f" Query params: {params}")
|
|
114
|
+
if json_data:
|
|
115
|
+
logger.debug(f" JSON payload: {json.dumps(json_data)[:200]}...")
|
|
116
|
+
|
|
117
|
+
response = requests.request(
|
|
118
|
+
method=method,
|
|
119
|
+
url=url,
|
|
120
|
+
headers=headers,
|
|
121
|
+
json=json_data,
|
|
122
|
+
params=params,
|
|
123
|
+
timeout=self.timeout_seconds,
|
|
124
|
+
verify=self.verify_ssl,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
logger.debug(f"GitHub response status: {response.status_code}")
|
|
128
|
+
if response.status_code >= 400:
|
|
129
|
+
logger.error(f"GitHub API error - Status: {response.status_code}, URL: {url}, Response: {response.text[:500]}")
|
|
130
|
+
|
|
131
|
+
self._handle_github_error(response)
|
|
132
|
+
|
|
133
|
+
if response.status_code == 204: # No Content
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
return response.json()
|
|
137
|
+
|
|
138
|
+
except requests.exceptions.Timeout as e:
|
|
139
|
+
raise IntegrationError(f"GitHub API request timeout: {e}") from e
|
|
140
|
+
except requests.exceptions.RequestException as e:
|
|
141
|
+
raise IntegrationError(f"GitHub API request failed: {e}") from e
|
|
142
|
+
|
|
143
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
144
|
+
"""GET request."""
|
|
145
|
+
return self.request("GET", endpoint, params=params)
|
|
146
|
+
|
|
147
|
+
def post(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
148
|
+
"""POST request."""
|
|
149
|
+
return self.request("POST", endpoint, json_data=json_data, params=params)
|
|
150
|
+
|
|
151
|
+
def put(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
152
|
+
"""PUT request."""
|
|
153
|
+
return self.request("PUT", endpoint, json_data=json_data, params=params)
|
|
154
|
+
|
|
155
|
+
def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
156
|
+
"""DELETE request."""
|
|
157
|
+
return self.request("DELETE", endpoint, params=params)
|
|
158
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trello client for creating tasks and recommendations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
from ....core.config import SamiConfig
|
|
10
|
+
from ....core.errors import IntegrationError
|
|
11
|
+
from ....core.logging import get_logger
|
|
12
|
+
from .trello_http import TrelloHttpClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger("sami.integrations.trello.client")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TrelloClient:
|
|
19
|
+
"""
|
|
20
|
+
Client for interacting with Trello API to create tasks and recommendations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, http_client: TrelloHttpClient, fine_tuning_board_id: str, engineering_board_id: str) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Initialize Trello client.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
http_client: HTTP client for Trello API
|
|
29
|
+
fine_tuning_board_id: Trello board ID for fine-tuning recommendations
|
|
30
|
+
engineering_board_id: Trello board ID for engineering/visibility recommendations
|
|
31
|
+
"""
|
|
32
|
+
self._http = http_client
|
|
33
|
+
self.fine_tuning_board_id = fine_tuning_board_id
|
|
34
|
+
self.engineering_board_id = engineering_board_id
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_config(cls, config: SamiConfig) -> "TrelloClient":
|
|
38
|
+
"""
|
|
39
|
+
Factory to construct a client from ``SamiConfig``.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: SamiConfig instance with Trello configuration
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
TrelloClient instance
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
IntegrationError: If Trello configuration is not set
|
|
49
|
+
"""
|
|
50
|
+
if not config.eng or not config.eng.trello:
|
|
51
|
+
raise IntegrationError("Trello configuration is not set in SamiConfig")
|
|
52
|
+
|
|
53
|
+
trello_config = config.eng.trello
|
|
54
|
+
http_client = TrelloHttpClient(
|
|
55
|
+
api_key=trello_config.api_key,
|
|
56
|
+
api_token=trello_config.api_token,
|
|
57
|
+
timeout_seconds=trello_config.timeout_seconds,
|
|
58
|
+
verify_ssl=trello_config.verify_ssl,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return cls(
|
|
62
|
+
http_client=http_client,
|
|
63
|
+
fine_tuning_board_id=trello_config.fine_tuning_board_id,
|
|
64
|
+
engineering_board_id=trello_config.engineering_board_id,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def create_fine_tuning_recommendation(
|
|
68
|
+
self,
|
|
69
|
+
title: str,
|
|
70
|
+
description: str,
|
|
71
|
+
list_name: Optional[str] = None,
|
|
72
|
+
labels: Optional[list[str]] = None,
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
"""
|
|
75
|
+
Create a fine-tuning recommendation card on the fine-tuning board.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
title: Card title
|
|
79
|
+
description: Card description
|
|
80
|
+
list_name: Optional list name (defaults to first list on board)
|
|
81
|
+
labels: Optional list of label names to add to the card
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary with card information
|
|
85
|
+
"""
|
|
86
|
+
logger.info(f"Creating fine-tuning recommendation: {title}")
|
|
87
|
+
|
|
88
|
+
# Get lists on the board
|
|
89
|
+
lists = self._http.get(f"/1/boards/{self.fine_tuning_board_id}/lists")
|
|
90
|
+
|
|
91
|
+
# Find the target list
|
|
92
|
+
target_list_id = None
|
|
93
|
+
if list_name:
|
|
94
|
+
for list_item in lists:
|
|
95
|
+
if list_item.get("name") == list_name:
|
|
96
|
+
target_list_id = list_item.get("id")
|
|
97
|
+
break
|
|
98
|
+
if not target_list_id:
|
|
99
|
+
raise IntegrationError(f"List '{list_name}' not found on fine-tuning board")
|
|
100
|
+
else:
|
|
101
|
+
# Use the first list if no list_name specified
|
|
102
|
+
if not lists:
|
|
103
|
+
raise IntegrationError("No lists found on fine-tuning board")
|
|
104
|
+
target_list_id = lists[0].get("id")
|
|
105
|
+
|
|
106
|
+
# Create the card
|
|
107
|
+
card_data = {
|
|
108
|
+
"name": title,
|
|
109
|
+
"desc": description,
|
|
110
|
+
"idList": target_list_id,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
card = self._http.post("/1/cards", json_data=card_data)
|
|
114
|
+
|
|
115
|
+
# Add labels if provided
|
|
116
|
+
if labels:
|
|
117
|
+
board_labels = self._http.get(f"/1/boards/{self.fine_tuning_board_id}/labels")
|
|
118
|
+
label_map = {label.get("name"): label.get("id") for label in board_labels if label.get("name")}
|
|
119
|
+
|
|
120
|
+
for label_name in labels:
|
|
121
|
+
if label_name in label_map:
|
|
122
|
+
# Trello API uses POST /1/cards/{id}/idLabels with value parameter
|
|
123
|
+
self._http.post(f"/1/cards/{card['id']}/idLabels", params={"value": label_map[label_name]})
|
|
124
|
+
else:
|
|
125
|
+
logger.warning(f"Label '{label_name}' not found on board, skipping")
|
|
126
|
+
|
|
127
|
+
logger.info(f"Created fine-tuning recommendation card: {card.get('id')}")
|
|
128
|
+
return card
|
|
129
|
+
|
|
130
|
+
def create_visibility_recommendation(
|
|
131
|
+
self,
|
|
132
|
+
title: str,
|
|
133
|
+
description: str,
|
|
134
|
+
list_name: Optional[str] = None,
|
|
135
|
+
labels: Optional[list[str]] = None,
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Create a visibility/engineering recommendation card on the engineering board.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
title: Card title
|
|
142
|
+
description: Card description
|
|
143
|
+
list_name: Optional list name (defaults to first list on board)
|
|
144
|
+
labels: Optional list of label names to add to the card
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dictionary with card information
|
|
148
|
+
"""
|
|
149
|
+
logger.info(f"Creating visibility recommendation: {title}")
|
|
150
|
+
|
|
151
|
+
# Get lists on the board
|
|
152
|
+
lists = self._http.get(f"/1/boards/{self.engineering_board_id}/lists")
|
|
153
|
+
|
|
154
|
+
# Find the target list
|
|
155
|
+
target_list_id = None
|
|
156
|
+
if list_name:
|
|
157
|
+
for list_item in lists:
|
|
158
|
+
if list_item.get("name") == list_name:
|
|
159
|
+
target_list_id = list_item.get("id")
|
|
160
|
+
break
|
|
161
|
+
if not target_list_id:
|
|
162
|
+
raise IntegrationError(f"List '{list_name}' not found on engineering board")
|
|
163
|
+
else:
|
|
164
|
+
# Use the first list if no list_name specified
|
|
165
|
+
if not lists:
|
|
166
|
+
raise IntegrationError("No lists found on engineering board")
|
|
167
|
+
target_list_id = lists[0].get("id")
|
|
168
|
+
|
|
169
|
+
# Create the card
|
|
170
|
+
card_data = {
|
|
171
|
+
"name": title,
|
|
172
|
+
"desc": description,
|
|
173
|
+
"idList": target_list_id,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
card = self._http.post("/1/cards", json_data=card_data)
|
|
177
|
+
|
|
178
|
+
# Add labels if provided
|
|
179
|
+
if labels:
|
|
180
|
+
board_labels = self._http.get(f"/1/boards/{self.engineering_board_id}/labels")
|
|
181
|
+
label_map = {label.get("name"): label.get("id") for label in board_labels if label.get("name")}
|
|
182
|
+
|
|
183
|
+
for label_name in labels:
|
|
184
|
+
if label_name in label_map:
|
|
185
|
+
# Trello API uses POST /1/cards/{id}/idLabels with value parameter
|
|
186
|
+
self._http.post(f"/1/cards/{card['id']}/idLabels", params={"value": label_map[label_name]})
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(f"Label '{label_name}' not found on board, skipping")
|
|
189
|
+
|
|
190
|
+
logger.info(f"Created visibility recommendation card: {card.get('id')}")
|
|
191
|
+
return card
|
|
192
|
+
|
|
193
|
+
def ping(self) -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Check if Trello API is reachable.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if API is reachable, False otherwise
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
# Try to get boards for the authenticated user
|
|
202
|
+
self._http.get("/1/members/me/boards", params={"limit": 1})
|
|
203
|
+
return True
|
|
204
|
+
except IntegrationError:
|
|
205
|
+
logger.exception("Trello ping failed")
|
|
206
|
+
return False
|
|
207
|
+
|