agentops-cockpit 0.5.0__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.
Files changed (38) hide show
  1. agent_ops_cockpit/agent.py +137 -0
  2. agent_ops_cockpit/cli/main.py +104 -11
  3. agent_ops_cockpit/eval/load_test.py +15 -10
  4. agent_ops_cockpit/eval/quality_climber.py +23 -5
  5. agent_ops_cockpit/eval/red_team.py +5 -4
  6. agent_ops_cockpit/mcp_server.py +55 -21
  7. agent_ops_cockpit/ops/arch_review.py +78 -17
  8. agent_ops_cockpit/ops/cost_optimizer.py +0 -1
  9. agent_ops_cockpit/ops/evidence_bridge.py +132 -0
  10. agent_ops_cockpit/ops/frameworks.py +79 -10
  11. agent_ops_cockpit/ops/mcp_hub.py +1 -2
  12. agent_ops_cockpit/ops/orchestrator.py +363 -49
  13. agent_ops_cockpit/ops/pii_scrubber.py +1 -1
  14. agent_ops_cockpit/ops/policies.json +26 -0
  15. agent_ops_cockpit/ops/policy_engine.py +85 -0
  16. agent_ops_cockpit/ops/reliability.py +30 -10
  17. agent_ops_cockpit/ops/secret_scanner.py +10 -3
  18. agent_ops_cockpit/ops/ui_auditor.py +52 -11
  19. agent_ops_cockpit/ops/watcher.py +138 -0
  20. agent_ops_cockpit/ops/watchlist.json +88 -0
  21. agent_ops_cockpit/optimizer.py +361 -53
  22. agent_ops_cockpit/shadow/router.py +7 -8
  23. agent_ops_cockpit/system_prompt.md +13 -0
  24. agent_ops_cockpit/tests/golden_set.json +52 -0
  25. agent_ops_cockpit/tests/test_agent.py +34 -0
  26. agent_ops_cockpit/tests/test_arch_review.py +45 -0
  27. agent_ops_cockpit/tests/test_frameworks.py +100 -0
  28. agent_ops_cockpit/tests/test_optimizer.py +68 -0
  29. agent_ops_cockpit/tests/test_quality_climber.py +18 -0
  30. agent_ops_cockpit/tests/test_red_team.py +35 -0
  31. agent_ops_cockpit/tests/test_secret_scanner.py +24 -0
  32. agentops_cockpit-0.9.5.dist-info/METADATA +246 -0
  33. agentops_cockpit-0.9.5.dist-info/RECORD +47 -0
  34. {agentops_cockpit-0.5.0.dist-info → agentops_cockpit-0.9.5.dist-info}/entry_points.txt +1 -1
  35. agentops_cockpit-0.5.0.dist-info/METADATA +0 -171
  36. agentops_cockpit-0.5.0.dist-info/RECORD +0 -32
  37. {agentops_cockpit-0.5.0.dist-info → agentops_cockpit-0.9.5.dist-info}/WHEEL +0 -0
  38. {agentops_cockpit-0.5.0.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", "src/backend/tests"]):
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 as e:
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
- console.print(table)
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)
@@ -8,6 +8,7 @@ from rich.panel import Panel
8
8
  app = typer.Typer(help="Face Auditor: Scan frontend code for A2UI alignment.")
9
9
  console = Console()
10
10
 
11
+ @app.command()
11
12
  def audit(path: str = "src"):
12
13
  """
13
14
  Step 4: Frontend / A2UI Auditing.
@@ -23,6 +24,10 @@ def audit(path: str = "src"):
23
24
  surface_id_pattern = re.compile(r"surfaceId|['\"]surface-id['\"]")
24
25
  registry_pattern = re.compile(r"A2UIRegistry|registerComponent")
25
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")
26
31
 
27
32
  for root, dirs, files in os.walk(path):
28
33
  if any(d in root for d in [".venv", "node_modules", ".git", "dist"]):
@@ -32,37 +37,73 @@ def audit(path: str = "src"):
32
37
  if file.endswith((".tsx", ".ts", ".js", ".jsx")):
33
38
  files_scanned += 1
34
39
  file_path = os.path.join(root, file)
40
+ rel_path = os.path.relpath(file_path, ".")
35
41
  try:
36
42
  with open(file_path, 'r') as f:
37
- content = f.read()
43
+ lines = f.readlines()
44
+ content = "".join(lines)
38
45
 
39
46
  findings = []
47
+
48
+ # Heuristic with Line Numbers
40
49
  if not surface_id_pattern.search(content):
41
- findings.append("Missing 'surfaceId' mapping")
50
+ findings.append({"line": 1, "issue": "Missing 'surfaceId' mapping", "fix": "Add 'surfaceId' prop to the root component or exported interface."})
51
+
42
52
  if not registry_pattern.search(content) and "Registry" in file:
43
- findings.append("Registry component without A2UIRegistry registration")
53
+ findings.append({"line": 1, "issue": "Registry component without A2UIRegistry registration", "fix": "Wrap component in A2UIRegistry.registerComponent()."})
54
+
44
55
  if "Button" in file and not trigger_pattern.search(content):
45
- findings.append("Interactive component without Tool/Agent triggers")
46
-
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)."})
63
+
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."})
74
+
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."})
80
+
47
81
  if findings:
48
- issues.append({"file": file, "findings": findings})
49
- except:
82
+ issues.append({"file": rel_path, "findings": findings})
83
+
84
+ except Exception:
50
85
  pass
51
86
 
52
87
  console.print(f"📝 Scanned [bold]{files_scanned}[/bold] frontend files.")
53
88
 
54
89
  table = Table(title="🔍 A2UI Audit Findings")
55
- table.add_column("File", style="cyan")
90
+ table.add_column("File:Line", style="cyan")
56
91
  table.add_column("Issue", style="red")
92
+ table.add_column("Recommended Fix", style="green")
57
93
 
58
94
  if not issues:
59
- table.add_row("All Files", "[green]A2UI Ready[/green]")
95
+ table.add_row("All Files", "[green]A2UI Ready[/green]", "No action needed.")
60
96
  else:
97
+ # Structured output for Orchestrator parsing
98
+ console.print("\n[bold]🛠️ DEVELOPER ACTIONS REQUIRED:[/bold]")
61
99
  for issue in issues:
62
100
  for finding in issue["findings"]:
63
- table.add_row(issue["file"], finding)
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)
64
106
 
65
- console.print(table)
66
107
 
67
108
  if len(issues) > 5:
68
109
  console.print("\n⚠️ [yellow]Recommendation:[/yellow] Your 'Face' layer has fragmented A2UI surface mappings.")
@@ -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
+ }