agentops-cockpit 0.4.1__py3-none-any.whl → 0.9.5__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.
- agent_ops_cockpit/agent.py +137 -0
- agent_ops_cockpit/cli/main.py +114 -11
- agent_ops_cockpit/eval/load_test.py +15 -10
- agent_ops_cockpit/eval/quality_climber.py +23 -5
- agent_ops_cockpit/eval/red_team.py +16 -10
- agent_ops_cockpit/mcp_server.py +132 -0
- agent_ops_cockpit/ops/arch_review.py +125 -59
- agent_ops_cockpit/ops/cost_optimizer.py +0 -1
- agent_ops_cockpit/ops/evidence_bridge.py +132 -0
- agent_ops_cockpit/ops/frameworks.py +79 -10
- agent_ops_cockpit/ops/mcp_hub.py +1 -2
- agent_ops_cockpit/ops/orchestrator.py +363 -49
- agent_ops_cockpit/ops/pii_scrubber.py +1 -1
- agent_ops_cockpit/ops/policies.json +26 -0
- agent_ops_cockpit/ops/policy_engine.py +85 -0
- agent_ops_cockpit/ops/reliability.py +30 -10
- agent_ops_cockpit/ops/secret_scanner.py +10 -3
- agent_ops_cockpit/ops/ui_auditor.py +91 -96
- agent_ops_cockpit/ops/watcher.py +138 -0
- agent_ops_cockpit/ops/watchlist.json +88 -0
- agent_ops_cockpit/optimizer.py +380 -158
- agent_ops_cockpit/shadow/router.py +7 -8
- agent_ops_cockpit/system_prompt.md +13 -0
- agent_ops_cockpit/tests/golden_set.json +52 -0
- agent_ops_cockpit/tests/test_agent.py +34 -0
- agent_ops_cockpit/tests/test_arch_review.py +45 -0
- agent_ops_cockpit/tests/test_frameworks.py +100 -0
- agent_ops_cockpit/tests/test_optimizer.py +68 -0
- agent_ops_cockpit/tests/test_quality_climber.py +18 -0
- agent_ops_cockpit/tests/test_red_team.py +35 -0
- agent_ops_cockpit/tests/test_secret_scanner.py +24 -0
- agentops_cockpit-0.9.5.dist-info/METADATA +246 -0
- agentops_cockpit-0.9.5.dist-info/RECORD +47 -0
- {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/entry_points.txt +1 -0
- agentops_cockpit-0.4.1.dist-info/METADATA +0 -171
- agentops_cockpit-0.4.1.dist-info/RECORD +0 -31
- {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/WHEEL +0 -0
- {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,6 +12,9 @@ console = Console()
|
|
|
12
12
|
SECRET_PATTERNS = {
|
|
13
13
|
"Google API Key": r"AIza[0-9A-Za-z-_]{35}",
|
|
14
14
|
"AWS Access Key": r"AKIA[0-9A-Z]{16}",
|
|
15
|
+
"OpenAI API Key": r"sk-[a-zA-Z0-9]{20,}",
|
|
16
|
+
"Anthropic API Key": r"sk-ant-[a-zA-Z0-9]{20,}",
|
|
17
|
+
"Azure OpenAI Key": r"[0-9a-f]{32}",
|
|
15
18
|
"Generic Bearer Token": r"Bearer\s+[0-9a-zA-Z._-]{20,}",
|
|
16
19
|
"Hardcoded API Variable": r"(?i)(api_key|app_secret|client_secret|access_token)\s*=\s*['\"][0-9a-zA-Z_-]{16,}['\"]",
|
|
17
20
|
"GCP Service Account": r"\"type\":\s*\"service_account\"",
|
|
@@ -28,7 +31,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
|
|
|
28
31
|
|
|
29
32
|
for root, dirs, files in os.walk(path):
|
|
30
33
|
# Skip virtual environments, git, and tests
|
|
31
|
-
if any(skip in root for skip in [".venv", ".git", "
|
|
34
|
+
if any(skip in root.lower() for skip in [".venv", ".git", "tests", "test_", "node_modules"]):
|
|
32
35
|
continue
|
|
33
36
|
|
|
34
37
|
for file in files:
|
|
@@ -47,7 +50,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
|
|
|
47
50
|
"type": secret_name,
|
|
48
51
|
"content": line.strip()[:50] + "..."
|
|
49
52
|
})
|
|
50
|
-
except Exception
|
|
53
|
+
except Exception:
|
|
51
54
|
continue
|
|
52
55
|
|
|
53
56
|
table = Table(title="🛡️ Security Findings: Hardcoded Secrets")
|
|
@@ -57,6 +60,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
|
|
|
57
60
|
table.add_column("Suggestion", style="green")
|
|
58
61
|
|
|
59
62
|
if findings:
|
|
63
|
+
console.print("\n[bold]🛠️ DEVELOPER ACTIONS REQUIRED:[/bold]")
|
|
60
64
|
for finding in findings:
|
|
61
65
|
table.add_row(
|
|
62
66
|
finding["file"],
|
|
@@ -64,7 +68,10 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
|
|
|
64
68
|
finding["type"],
|
|
65
69
|
"Move to Secret Manager"
|
|
66
70
|
)
|
|
67
|
-
|
|
71
|
+
# Orchestrator parsing
|
|
72
|
+
console.print(f"ACTION: {finding['file']}:{finding['line']} | Found {finding['type']} leak | Move this credential to Google Cloud Secret Manager or .env file.")
|
|
73
|
+
|
|
74
|
+
console.print("\n", table)
|
|
68
75
|
console.print(f"\n❌ [bold red]FAIL:[/bold red] Found {len(findings)} potential credential leaks.")
|
|
69
76
|
console.print("💡 [bold green]Recommendation:[/bold green] Use Google Cloud Secret Manager or environment variables for all tokens.")
|
|
70
77
|
raise typer.Exit(code=1)
|
|
@@ -5,116 +5,111 @@ from rich.console import Console
|
|
|
5
5
|
from rich.table import Table
|
|
6
6
|
from rich.panel import Panel
|
|
7
7
|
|
|
8
|
-
app = typer.Typer(help="
|
|
8
|
+
app = typer.Typer(help="Face Auditor: Scan frontend code for A2UI alignment.")
|
|
9
9
|
console = Console()
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
@app.command()
|
|
12
|
+
def audit(path: str = "src"):
|
|
13
|
+
"""
|
|
14
|
+
Step 4: Frontend / A2UI Auditing.
|
|
15
|
+
Ensures frontend components are properly mapping surfaceId and detecting triggers.
|
|
16
|
+
"""
|
|
17
|
+
console.print(Panel.fit("🎭 [bold blue]FACE AUDITOR: A2UI COMPONENT SCAN[/bold blue]", border_style="blue"))
|
|
18
|
+
console.print(f"Scanning directory: [yellow]{path}[/yellow]")
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
files_scanned = 0
|
|
21
|
+
issues = []
|
|
22
|
+
|
|
23
|
+
# Heuristic Patterns
|
|
24
|
+
surface_id_pattern = re.compile(r"surfaceId|['\"]surface-id['\"]")
|
|
25
|
+
registry_pattern = re.compile(r"A2UIRegistry|registerComponent")
|
|
26
|
+
trigger_pattern = re.compile(r"onTrigger|handleTrigger|agentAction")
|
|
27
|
+
ux_feedback_pattern = re.compile(r"Skeleton|Loading|Spinner|Progress")
|
|
28
|
+
a11y_pattern = re.compile(r"aria-label|role=|tabIndex|alt=")
|
|
29
|
+
legal_pattern = re.compile(r"Copyright|PrivacyPolicy|Disclaimer|TermsOfService|©")
|
|
30
|
+
marketing_pattern = re.compile(r"og:image|meta\s+name=['\"]description['\"]|favicon|logo")
|
|
31
|
+
|
|
32
|
+
for root, dirs, files in os.walk(path):
|
|
33
|
+
if any(d in root for d in [".venv", "node_modules", ".git", "dist"]):
|
|
34
|
+
continue
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
for file in files:
|
|
37
|
+
if file.endswith((".tsx", ".ts", ".js", ".jsx")):
|
|
38
|
+
files_scanned += 1
|
|
39
|
+
file_path = os.path.join(root, file)
|
|
40
|
+
rel_path = os.path.relpath(file_path, ".")
|
|
31
41
|
try:
|
|
32
|
-
with open(
|
|
33
|
-
|
|
42
|
+
with open(file_path, 'r') as f:
|
|
43
|
+
lines = f.readlines()
|
|
44
|
+
content = "".join(lines)
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
findings = []
|
|
47
|
+
|
|
48
|
+
# Heuristic with Line Numbers
|
|
49
|
+
if not surface_id_pattern.search(content):
|
|
50
|
+
findings.append({"line": 1, "issue": "Missing 'surfaceId' mapping", "fix": "Add 'surfaceId' prop to the root component or exported interface."})
|
|
51
|
+
|
|
52
|
+
if not registry_pattern.search(content) and "Registry" in file:
|
|
53
|
+
findings.append({"line": 1, "issue": "Registry component without A2UIRegistry registration", "fix": "Wrap component in A2UIRegistry.registerComponent()."})
|
|
54
|
+
|
|
55
|
+
if "Button" in file and not trigger_pattern.search(content):
|
|
56
|
+
# Try to find the button line
|
|
57
|
+
line_no = 1
|
|
58
|
+
for i, line in enumerate(lines):
|
|
59
|
+
if "<button" in line.lower() or "<Button" in line:
|
|
60
|
+
line_no = i + 1
|
|
61
|
+
break
|
|
62
|
+
findings.append({"line": line_no, "issue": "Interactive component without Tool/Agent triggers", "fix": "Ensure the button calls an agent tool trigger (onTrigger)."})
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
))
|
|
64
|
+
if not ux_feedback_pattern.search(content) and ("Page" in file or "View" in file):
|
|
65
|
+
findings.append({"line": 1, "issue": "Missing 'Thinking' feedback (Skeleton/Spinner)", "fix": "Implement a Loading state or Skeleton component for agent latency."})
|
|
66
|
+
|
|
67
|
+
if not a11y_pattern.search(content) and ("Button" in file or "Input" in file):
|
|
68
|
+
line_no = 1
|
|
69
|
+
for i, line in enumerate(lines):
|
|
70
|
+
if "<button" in line.lower() or "<input" in line.lower():
|
|
71
|
+
line_no = i + 1
|
|
72
|
+
break
|
|
73
|
+
findings.append({"line": line_no, "issue": "Missing i18n/Accessibility labels (aria-label)", "fix": "Add aria-label or alt tags for screen readers and i18n."})
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
rel_path, "Move tokens to .streamlit/secrets.toml."
|
|
65
|
-
))
|
|
75
|
+
if not legal_pattern.search(content) and ("Page" in file or "Layout" in file or "Footer" in file):
|
|
76
|
+
findings.append({"line": 1, "issue": "Missing Legal Disclaimer or Privacy Policy link", "fix": "Add a footer link to the mandatory Privacy Policy / TOS."})
|
|
77
|
+
|
|
78
|
+
if not marketing_pattern.search(content) and ("index" in file.lower() or "head" in file.lower() or "App" in file):
|
|
79
|
+
findings.append({"line": 1, "issue": "Missing Branding (Logo) or SEO Metadata (OG/Description)", "fix": "Add meta tags (og:image, description) and project logo."})
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
findings.append(UIFinding(
|
|
70
|
-
"angular_reactivity", "Angular Performance", "MEDIUM",
|
|
71
|
-
"Component lacks reactive patterns (Signals/Observables).",
|
|
72
|
-
rel_path, "Use Signals for low-latency Agent output sync."
|
|
73
|
-
))
|
|
81
|
+
if findings:
|
|
82
|
+
issues.append({"file": rel_path, "findings": findings})
|
|
74
83
|
|
|
75
|
-
except:
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
with open(path, "r", errors="ignore") as f:
|
|
80
|
-
content = f.read()
|
|
81
|
-
|
|
82
|
-
# 4. Responsive: Missing Media Queries
|
|
83
|
-
if "@media" not in content:
|
|
84
|
-
findings.append(UIFinding(
|
|
85
|
-
"missing_media_queries", "UX / Responsive", "HIGH",
|
|
86
|
-
"No media queries detected in CSS file.",
|
|
87
|
-
rel_path, "Implement mobile-first responsive design."
|
|
88
|
-
))
|
|
89
|
-
except: continue
|
|
90
|
-
|
|
91
|
-
return findings
|
|
87
|
+
console.print(f"📝 Scanned [bold]{files_scanned}[/bold] frontend files.")
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
"""
|
|
98
|
-
console.print(Panel.fit("🎨 [bold magenta]FACE AUDITOR: UI/UX GOVERNANCE[/bold magenta]", border_style="magenta"))
|
|
99
|
-
|
|
100
|
-
findings = audit_ui_best_practices(path)
|
|
101
|
-
|
|
102
|
-
table = Table(title="🎨 UI/UX Audit Results")
|
|
103
|
-
table.add_column("Category", style="cyan")
|
|
104
|
-
table.add_column("Severity", style="bold")
|
|
105
|
-
table.add_column("Message", style="white")
|
|
106
|
-
table.add_column("File", style="dim")
|
|
107
|
-
table.add_column("Suggestion", style="green")
|
|
89
|
+
table = Table(title="🔍 A2UI Audit Findings")
|
|
90
|
+
table.add_column("File:Line", style="cyan")
|
|
91
|
+
table.add_column("Issue", style="red")
|
|
92
|
+
table.add_column("Recommended Fix", style="green")
|
|
108
93
|
|
|
109
|
-
if
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
94
|
+
if not issues:
|
|
95
|
+
table.add_row("All Files", "[green]A2UI Ready[/green]", "No action needed.")
|
|
96
|
+
else:
|
|
97
|
+
# Structured output for Orchestrator parsing
|
|
98
|
+
console.print("\n[bold]🛠️ DEVELOPER ACTIONS REQUIRED:[/bold]")
|
|
99
|
+
for issue in issues:
|
|
100
|
+
for finding in issue["findings"]:
|
|
101
|
+
table.add_row(f"{issue['file']}:{finding['line']}", finding["issue"], finding["fix"])
|
|
102
|
+
# This line is for the orchestrator to parse easily
|
|
103
|
+
console.print(f"ACTION: {issue['file']}:{finding['line']} | {finding['issue']} | {finding['fix']}")
|
|
104
|
+
|
|
105
|
+
console.print("\n", table)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if len(issues) > 5:
|
|
109
|
+
console.print("\n⚠️ [yellow]Recommendation:[/yellow] Your 'Face' layer has fragmented A2UI surface mappings.")
|
|
110
|
+
console.print("💡 Use the A2UI Registry to unify how your agent logic triggers visual surfaces.")
|
|
116
111
|
else:
|
|
117
|
-
console.print("✅ [bold green]
|
|
112
|
+
console.print("\n✅ [bold green]Frontend is Well-Architected for GenUI interactions.[/bold green]")
|
|
118
113
|
|
|
119
114
|
if __name__ == "__main__":
|
|
120
115
|
app()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import urllib.request
|
|
4
|
+
import xml.etree.ElementTree as ET
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
import importlib.metadata
|
|
10
|
+
from packaging import version
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
WATCHLIST_PATH = os.path.join(os.path.dirname(__file__), "watchlist.json")
|
|
18
|
+
|
|
19
|
+
def get_local_version(package_name: str) -> str:
|
|
20
|
+
try:
|
|
21
|
+
return importlib.metadata.version(package_name)
|
|
22
|
+
except importlib.metadata.PackageNotFoundError:
|
|
23
|
+
return "Not Installed"
|
|
24
|
+
|
|
25
|
+
def clean_version(v_str: str) -> str:
|
|
26
|
+
"""Extracts a clean version number from strings like 'v1.2.3', 'package==1.2.3', '2026-01-28 (v0.1.0)'"""
|
|
27
|
+
# Look for patterns like X.Y.Z or X.Y
|
|
28
|
+
match = re.search(r'(\d+\.\d+(?:\.\d+)?(?:[a-zA-Z]+\d+)?)', v_str)
|
|
29
|
+
if match:
|
|
30
|
+
return match.group(1)
|
|
31
|
+
return v_str.strip().lstrip('v')
|
|
32
|
+
|
|
33
|
+
def fetch_latest_from_atom(url: str) -> Optional[Dict[str, str]]:
|
|
34
|
+
try:
|
|
35
|
+
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
36
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
37
|
+
tree = ET.parse(response)
|
|
38
|
+
root = tree.getroot()
|
|
39
|
+
ns = {'ns': 'http://www.w3.org/2005/Atom'}
|
|
40
|
+
|
|
41
|
+
latest_entry = root.find('ns:entry', ns)
|
|
42
|
+
if latest_entry is not None:
|
|
43
|
+
title = latest_entry.find('ns:title', ns).text
|
|
44
|
+
updated = latest_entry.find('ns:updated', ns).text
|
|
45
|
+
raw_v = title.strip().split()[-1]
|
|
46
|
+
return {
|
|
47
|
+
"version": clean_version(raw_v) if "==" not in raw_v else clean_version(raw_v.split("==")[-1]),
|
|
48
|
+
"date": updated,
|
|
49
|
+
"title": title
|
|
50
|
+
}
|
|
51
|
+
except Exception:
|
|
52
|
+
# console.print(f"[dim red]Error fetching {url}: {e}[/dim red]")
|
|
53
|
+
return None
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def run_watch():
|
|
57
|
+
console.print(Panel.fit("🔍 [bold blue]AGENTOPS COCKPIT: ECOSYSTEM WATCHER[/bold blue]", border_style="blue"))
|
|
58
|
+
|
|
59
|
+
if not os.path.exists(WATCHLIST_PATH):
|
|
60
|
+
console.print(f"❌ [red]Watchlist not found at {WATCHLIST_PATH}[/red]")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
with open(WATCHLIST_PATH, 'r') as f:
|
|
64
|
+
watchlist = json.load(f)
|
|
65
|
+
|
|
66
|
+
table = Table(title=f"Ecosystem Pulse - {datetime.now().strftime('%Y-%m-%d')}", show_header=True, header_style="bold magenta")
|
|
67
|
+
table.add_column("Category", style="cyan")
|
|
68
|
+
table.add_column("Component", style="white")
|
|
69
|
+
table.add_column("Local", style="yellow")
|
|
70
|
+
table.add_column("Latest", style="green")
|
|
71
|
+
table.add_column("Status", justify="center")
|
|
72
|
+
|
|
73
|
+
updates_found = []
|
|
74
|
+
|
|
75
|
+
for category, items in watchlist.items():
|
|
76
|
+
for name, info in items.items():
|
|
77
|
+
package = info.get("package")
|
|
78
|
+
local_v_raw = get_local_version(package) if package else None
|
|
79
|
+
local_v = local_v_raw if local_v_raw else "N/A"
|
|
80
|
+
|
|
81
|
+
with console.status(f"[dim]Checking {name}..."):
|
|
82
|
+
latest_info = fetch_latest_from_atom(info["feed"])
|
|
83
|
+
|
|
84
|
+
if latest_info:
|
|
85
|
+
latest_v = latest_info["version"]
|
|
86
|
+
|
|
87
|
+
is_outdated = False
|
|
88
|
+
if local_v_raw and local_v_raw != "Not Installed":
|
|
89
|
+
try:
|
|
90
|
+
is_outdated = version.parse(latest_v) > version.parse(local_v_raw)
|
|
91
|
+
except Exception:
|
|
92
|
+
is_outdated = latest_v > local_v_raw
|
|
93
|
+
|
|
94
|
+
status = "🚨 [bold red]UPDATE[/bold red]" if is_outdated else "✅ [green]OK[/green]"
|
|
95
|
+
if local_v == "Not Installed":
|
|
96
|
+
status = "➕ [dim]NEW[/dim]"
|
|
97
|
+
if package is None:
|
|
98
|
+
status = "🌐 [blue]SPEC[/blue]"
|
|
99
|
+
|
|
100
|
+
display_local = local_v if local_v != "Not Installed" else "[dim]Not Installed[/dim]"
|
|
101
|
+
table.add_row(
|
|
102
|
+
category.upper(),
|
|
103
|
+
name,
|
|
104
|
+
display_local,
|
|
105
|
+
latest_v,
|
|
106
|
+
status
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if is_outdated:
|
|
110
|
+
updates_found.append({
|
|
111
|
+
"name": name,
|
|
112
|
+
"current": local_v,
|
|
113
|
+
"latest": latest_v,
|
|
114
|
+
"package": package,
|
|
115
|
+
"desc": info["description"]
|
|
116
|
+
})
|
|
117
|
+
else:
|
|
118
|
+
table.add_row(category.upper(), name, local_v, "[red]Fetch Failed[/red]", "❓")
|
|
119
|
+
|
|
120
|
+
console.print(table)
|
|
121
|
+
|
|
122
|
+
if updates_found:
|
|
123
|
+
console.print("\n[bold yellow]⚠️ Actionable Intelligence:[/bold yellow]")
|
|
124
|
+
for up in updates_found:
|
|
125
|
+
console.print(f"• [bold]{up['name']}[/bold]: {up['current']} ➔ [bold green]{up['latest']}[/bold green]")
|
|
126
|
+
console.print(f" [dim]{up['desc']}[/dim]")
|
|
127
|
+
|
|
128
|
+
pkgs = " ".join([u['package'] for u in updates_found if u['package']])
|
|
129
|
+
if pkgs:
|
|
130
|
+
console.print(f"\n[bold cyan]Pro-tip:[/bold cyan] Run `pip install --upgrade {pkgs}` to sync.")
|
|
131
|
+
|
|
132
|
+
# Exit with special code to signal updates to CI/CD
|
|
133
|
+
sys.exit(2)
|
|
134
|
+
else:
|
|
135
|
+
console.print("\n[bold green]✨ All components are currently in sync with the latest stable releases.[/bold green]")
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
run_watch()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"orchestration_frameworks": {
|
|
3
|
+
"google-adk": {
|
|
4
|
+
"feed": "https://github.com/google/adk-python/releases.atom",
|
|
5
|
+
"package": "google-adk",
|
|
6
|
+
"description": "Agent Development Kit - Core reasoning & tool orchestration",
|
|
7
|
+
"min_version_for_optimizations": "0.15.0"
|
|
8
|
+
},
|
|
9
|
+
"crewai": {
|
|
10
|
+
"feed": "https://github.com/crewAIInc/crewAI/releases.atom",
|
|
11
|
+
"package": "crewai",
|
|
12
|
+
"description": "CrewAI - Role-playing multi-agent systems",
|
|
13
|
+
"min_version_for_optimizations": "1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"langgraph": {
|
|
16
|
+
"feed": "https://github.com/langchain-ai/langgraph/releases.atom",
|
|
17
|
+
"package": "langgraph",
|
|
18
|
+
"description": "LangGraph - Stateful multi-agent orchestration",
|
|
19
|
+
"min_version_for_optimizations": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"autogen": {
|
|
22
|
+
"feed": "https://github.com/microsoft/autogen/releases.atom",
|
|
23
|
+
"package": "pyautogen",
|
|
24
|
+
"description": "Microsoft AutoGen - Conversational agent framework",
|
|
25
|
+
"min_version_for_optimizations": "0.4.0"
|
|
26
|
+
},
|
|
27
|
+
"openai-agents": {
|
|
28
|
+
"feed": "https://github.com/openai/openai-agents-python/releases.atom",
|
|
29
|
+
"package": "openai-agents",
|
|
30
|
+
"description": "OpenAI Agents SDK - Multi-agent workflows",
|
|
31
|
+
"min_version_for_optimizations": "0.5.0"
|
|
32
|
+
},
|
|
33
|
+
"copilotkit": {
|
|
34
|
+
"feed": "https://github.com/CopilotKit/CopilotKit/releases.atom",
|
|
35
|
+
"package": "@copilotkit/react-core",
|
|
36
|
+
"description": "In-app AI Copilots & UX components",
|
|
37
|
+
"min_version_for_optimizations": "1.20.0"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"platform_services": {
|
|
41
|
+
"agent-engine": {
|
|
42
|
+
"feed": "https://github.com/googleapis/python-aiplatform/releases.atom",
|
|
43
|
+
"package": "google-cloud-aiplatform",
|
|
44
|
+
"description": "Vertex AI Agent Engine - Managed agent execution",
|
|
45
|
+
"min_version_for_optimizations": "1.70.0"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"compatibility_rules": [
|
|
49
|
+
{
|
|
50
|
+
"component": "langgraph",
|
|
51
|
+
"works_well_with": [
|
|
52
|
+
"google-adk",
|
|
53
|
+
"openai-agents"
|
|
54
|
+
],
|
|
55
|
+
"incompatible_with": [
|
|
56
|
+
"crewai"
|
|
57
|
+
],
|
|
58
|
+
"reason": "CrewAI and LangGraph both attempt to manage the orchestration loop and state, leading to cyclic-dependency conflicts."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"component": "google-adk",
|
|
62
|
+
"works_well_with": [
|
|
63
|
+
"langgraph",
|
|
64
|
+
"mcp"
|
|
65
|
+
],
|
|
66
|
+
"incompatible_with": [
|
|
67
|
+
"pyautogen"
|
|
68
|
+
],
|
|
69
|
+
"reason": "AutoGen's conversational loop pattern conflicts with ADK's strictly typed tool orchestration."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"component": "mcp",
|
|
73
|
+
"works_well_with": [
|
|
74
|
+
"google-adk",
|
|
75
|
+
"langgraph",
|
|
76
|
+
"openai-agents"
|
|
77
|
+
],
|
|
78
|
+
"incompatible_with": [],
|
|
79
|
+
"reason": "MCP is a universal standard and thrives in multi-framework environments."
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"research_sources": {
|
|
83
|
+
"well_architected": "https://cloud.google.com/architecture/framework",
|
|
84
|
+
"security_best_practices": "https://cloud.google.com/architecture/framework/security",
|
|
85
|
+
"cost_optimization": "https://cloud.google.com/architecture/framework/cost-optimization",
|
|
86
|
+
"operational_excellence": "https://cloud.google.com/architecture/framework/operational-excellence"
|
|
87
|
+
}
|
|
88
|
+
}
|