acra-cli 0.1.1__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.
- acra/__init__.py +7 -0
- acra/__main__.py +4 -0
- acra/agents/__init__.py +0 -0
- acra/agents/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm_utils.cpython-312.pyc +0 -0
- acra/agents/coder/__init__.py +0 -0
- acra/agents/coder/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_agent.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_chain.cpython-312.pyc +0 -0
- acra/agents/coder/coder_agent.py +235 -0
- acra/agents/coder/coder_chain.py +204 -0
- acra/agents/critic/__init__.py +0 -0
- acra/agents/critic/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_agent.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_chain.cpython-312.pyc +0 -0
- acra/agents/critic/critic_agent.py +199 -0
- acra/agents/critic/critic_chain.py +268 -0
- acra/agents/executor/__init__.py +0 -0
- acra/agents/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/docker_runner.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/executor_agent.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/sandbox_runner.cpython-312.pyc +0 -0
- acra/agents/executor/docker_runner.py +114 -0
- acra/agents/executor/executor_agent.py +142 -0
- acra/agents/executor/sandbox_runner.py +144 -0
- acra/agents/human/__init__.py +0 -0
- acra/agents/human/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/human/__pycache__/human_node.cpython-312.pyc +0 -0
- acra/agents/human/human_node.py +37 -0
- acra/agents/llm.py +263 -0
- acra/agents/llm_utils.py +161 -0
- acra/agents/memory/__init__.py +0 -0
- acra/agents/memory/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_agent.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_manager.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/retrieval.cpython-312.pyc +0 -0
- acra/agents/memory/memory_agent.py +239 -0
- acra/agents/memory/memory_manager.py +273 -0
- acra/agents/memory/retrieval.py +355 -0
- acra/agents/planner/__init__.py +0 -0
- acra/agents/planner/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_agent.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_chain.cpython-312.pyc +0 -0
- acra/agents/planner/planner_agent.py +97 -0
- acra/agents/planner/planner_chain.py +160 -0
- acra/agents/researcher/__init__.py +1 -0
- acra/agents/researcher/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_agent.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_chain.cpython-312.pyc +0 -0
- acra/agents/researcher/researcher_agent.py +231 -0
- acra/agents/researcher/researcher_chain.py +193 -0
- acra/brain/__init__.py +6 -0
- acra/brain/model_registry.py +19 -0
- acra/brain/provider_manager.py +21 -0
- acra/cli.py +52 -0
- acra/commands/__init__.py +29 -0
- acra/commands/ask.py +49 -0
- acra/commands/brain.py +58 -0
- acra/commands/config.py +25 -0
- acra/commands/context.py +30 -0
- acra/commands/graph.py +18 -0
- acra/commands/keys.py +40 -0
- acra/commands/logs.py +13 -0
- acra/commands/memory.py +27 -0
- acra/commands/plugin.py +18 -0
- acra/commands/research.py +65 -0
- acra/commands/serve.py +13 -0
- acra/commands/session.py +20 -0
- acra/commands/utils.py +12 -0
- acra/config/__init__.py +36 -0
- acra/config/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/config/__pycache__/defaults.cpython-312.pyc +0 -0
- acra/config/__pycache__/profile_manager.cpython-312.pyc +0 -0
- acra/config/defaults.py +88 -0
- acra/config/profile_manager.py +87 -0
- acra/execution/__init__.py +1 -0
- acra/execution/logs/__init__.py +1 -0
- acra/execution/logs/error_logs/__init__.py +1 -0
- acra/execution/logs/execution_logs/__init__.py +1 -0
- acra/execution/sandbox/__init__.py +1 -0
- acra/execution/sandbox/docker_executor.py +209 -0
- acra/execution/sandbox/isolated_runner.py +148 -0
- acra/execution/sandbox/sandbox_manager.py +178 -0
- acra/graph/__init__.py +0 -0
- acra/graph/checkpoint.py +85 -0
- acra/graph/conditional_edges.py +103 -0
- acra/graph/edges.py +85 -0
- acra/graph/graph_visualizer.py +175 -0
- acra/graph/nodes.py +68 -0
- acra/graph/router.py +69 -0
- acra/graph/state.py +93 -0
- acra/graph/workflow.py +117 -0
- acra/memory/__init__.py +1 -0
- acra/memory/checkpoints/__init__.py +1 -0
- acra/memory/checkpoints/data/__init__.py +1 -0
- acra/memory/checkpoints/postgres_checkpoint.py +247 -0
- acra/memory/checkpoints/sqlite_checkpoint.py +309 -0
- acra/memory/chroma_db/__init__.py +1 -0
- acra/memory/conversation_memory/__init__.py +1 -0
- acra/memory/conversation_memory/long_term.py +229 -0
- acra/memory/conversation_memory/short_term.py +158 -0
- acra/memory/storage/__init__.py +1 -0
- acra/memory/vector_store/__init__.py +0 -0
- acra/memory/vector_store/chroma_store.py +260 -0
- acra/memory/vector_store/embeddings.py +43 -0
- acra/observability/__init__.py +1 -0
- acra/observability/langsmith_tracing.py +91 -0
- acra/observability/metrics.py +189 -0
- acra/observability/monitoring.py +162 -0
- acra/observability/token_tracking.py +131 -0
- acra/prompts/__init__.py +1 -0
- acra/schemas/__init__.py +0 -0
- acra/schemas/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/coder_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/critic_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/execution_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/planner_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/researcher_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/shared_schema.cpython-312.pyc +0 -0
- acra/schemas/coder_schema.py +257 -0
- acra/schemas/critic_schema.py +177 -0
- acra/schemas/execution_schema.py +74 -0
- acra/schemas/planner_schema.py +129 -0
- acra/schemas/researcher_schema.py +220 -0
- acra/schemas/shared_schema.py +329 -0
- acra/tools/__init__.py +1 -0
- acra/tools/code_tools/__init__.py +1 -0
- acra/tools/code_tools/file_reader.py +156 -0
- acra/tools/code_tools/file_writer.py +108 -0
- acra/tools/code_tools/python_repl.py +72 -0
- acra/tools/code_tools/terminal_runner.py +98 -0
- acra/tools/github_tools/__init__.py +1 -0
- acra/tools/github_tools/commit_generator.py +91 -0
- acra/tools/github_tools/github_search.py +113 -0
- acra/tools/github_tools/repo_analyzer.py +138 -0
- acra/tools/integration_test_helper.py +370 -0
- acra/tools/validation_tools/__init__.py +1 -0
- acra/tools/validation_tools/dependency_checker.py +101 -0
- acra/tools/validation_tools/environment_config_validator.py +353 -0
- acra/tools/validation_tools/http_validator.py +336 -0
- acra/tools/validation_tools/response_validator.py +407 -0
- acra/tools/validation_tools/security_checker.py +93 -0
- acra/tools/validation_tools/syntax_checker.py +75 -0
- acra/tools/validation_tools/web_framework_validator.py +382 -0
- acra/tools/web_tools/__init__.py +1 -0
- acra/tools/web_tools/arxiv_search.py +110 -0
- acra/tools/web_tools/serpapi_search.py +106 -0
- acra/tools/web_tools/tavily_search.py +96 -0
- acra/ui/__init__.py +8 -0
- acra/ui/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/ui/__pycache__/banner.cpython-312.pyc +0 -0
- acra/ui/__pycache__/components.cpython-312.pyc +0 -0
- acra/ui/__pycache__/shell.cpython-312.pyc +0 -0
- acra/ui/__pycache__/theme.cpython-312.pyc +0 -0
- acra/ui/banner.py +40 -0
- acra/ui/components.py +32 -0
- acra/ui/shell.py +91 -0
- acra/ui/theme.py +28 -0
- acra/utils/__init__.py +21 -0
- acra/utils/context_manager.py +20 -0
- acra/utils/keyring_manager.py +83 -0
- acra/utils/output_formatter.py +18 -0
- acra/utils/session_manager.py +59 -0
- acra_cli-0.1.1.dist-info/METADATA +616 -0
- acra_cli-0.1.1.dist-info/RECORD +169 -0
- acra_cli-0.1.1.dist-info/WHEEL +5 -0
- acra_cli-0.1.1.dist-info/licenses/LICENSE +661 -0
- acra_cli-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class FrameworkIssue:
|
|
8
|
+
"""Framework validation issue"""
|
|
9
|
+
severity: str # "error", "warning", "info"
|
|
10
|
+
category: str # "configuration", "routing", "dependency", etc.
|
|
11
|
+
message: str
|
|
12
|
+
line_hint: Optional[str] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WebFrameworkValidatorTool:
|
|
16
|
+
"""
|
|
17
|
+
Web framework-specific validation tool.
|
|
18
|
+
|
|
19
|
+
Responsibilities:
|
|
20
|
+
- Validate Flask applications
|
|
21
|
+
- Validate FastAPI applications
|
|
22
|
+
- Check routing definitions
|
|
23
|
+
- Verify middleware configuration
|
|
24
|
+
- Check for common framework issues
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Framework patterns
|
|
28
|
+
FLASK_PATTERNS = {
|
|
29
|
+
"app_initialization": r"from\s+flask\s+import\s+Flask|Flask\s*\(",
|
|
30
|
+
"routes": r"@app\.route\(",
|
|
31
|
+
"blueprints": r"Blueprint\s*\(",
|
|
32
|
+
"error_handlers": r"@app\.errorhandler",
|
|
33
|
+
"before_request": r"@app\.before_request",
|
|
34
|
+
"after_request": r"@app\.after_request",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
FASTAPI_PATTERNS = {
|
|
38
|
+
"app_initialization": r"from\s+fastapi\s+import\s+FastAPI|FastAPI\s*\(",
|
|
39
|
+
"routes": r"@app\.(get|post|put|delete|patch)",
|
|
40
|
+
"dependencies": r"from\s+fastapi\s+import\s+Depends",
|
|
41
|
+
"middleware": r"@app\.middleware",
|
|
42
|
+
"lifespan": r"lifespan\s*=",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
DJANGO_PATTERNS = {
|
|
46
|
+
"models": r"from\s+django\.db\s+import\s+models|class\s+.*\(models\.Model\)",
|
|
47
|
+
"views": r"from\s+django\.views\s+import|def\s+\w+\(request\)",
|
|
48
|
+
"urls": r"from\s+django\.urls\s+import",
|
|
49
|
+
"settings": r"INSTALLED_APPS|DEBUG\s*=",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def detect_framework(code: str) -> Optional[str]:
|
|
54
|
+
"""
|
|
55
|
+
Detect which web framework is used.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
code: Python source code
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Framework name or None
|
|
62
|
+
"""
|
|
63
|
+
if re.search(WebFrameworkValidatorTool.FASTAPI_PATTERNS["app_initialization"], code):
|
|
64
|
+
return "fastapi"
|
|
65
|
+
elif re.search(WebFrameworkValidatorTool.FLASK_PATTERNS["app_initialization"], code):
|
|
66
|
+
return "flask"
|
|
67
|
+
elif re.search(WebFrameworkValidatorTool.DJANGO_PATTERNS["models"], code):
|
|
68
|
+
return "django"
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def validate_flask_app(code: str) -> Dict:
|
|
73
|
+
"""
|
|
74
|
+
Validate Flask application.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
code: Flask application code
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Validation result with issues and recommendations
|
|
81
|
+
"""
|
|
82
|
+
issues: List[FrameworkIssue] = []
|
|
83
|
+
features_found = {}
|
|
84
|
+
|
|
85
|
+
# Check for Flask initialization
|
|
86
|
+
if not re.search(WebFrameworkValidatorTool.FLASK_PATTERNS["app_initialization"], code):
|
|
87
|
+
issues.append(FrameworkIssue(
|
|
88
|
+
severity="error",
|
|
89
|
+
category="initialization",
|
|
90
|
+
message="Flask app not initialized with Flask() constructor"
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
# Check for routes
|
|
94
|
+
routes = re.findall(r"@app\.route\(['\"]([^'\"]+)['\"]\)", code)
|
|
95
|
+
features_found["routes"] = len(routes)
|
|
96
|
+
|
|
97
|
+
if not routes:
|
|
98
|
+
issues.append(FrameworkIssue(
|
|
99
|
+
severity="warning",
|
|
100
|
+
category="routing",
|
|
101
|
+
message="No routes defined in Flask application"
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
# Check for missing app.run()
|
|
105
|
+
if "app.run(" not in code:
|
|
106
|
+
issues.append(FrameworkIssue(
|
|
107
|
+
severity="warning",
|
|
108
|
+
category="execution",
|
|
109
|
+
message="No app.run() call found - application may not start"
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
# Check for debug mode
|
|
113
|
+
if "debug=True" in code:
|
|
114
|
+
issues.append(FrameworkIssue(
|
|
115
|
+
severity="warning",
|
|
116
|
+
category="configuration",
|
|
117
|
+
message="Debug mode enabled - disable in production"
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
# Check for CORS
|
|
121
|
+
has_cors = "CORS" in code or "cors" in code.lower()
|
|
122
|
+
features_found["cors"] = has_cors
|
|
123
|
+
|
|
124
|
+
# Check for error handlers
|
|
125
|
+
error_handlers = len(re.findall(r"@app\.errorhandler", code))
|
|
126
|
+
features_found["error_handlers"] = error_handlers
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"framework": "flask",
|
|
130
|
+
"valid": len([i for i in issues if i.severity == "error"]) == 0,
|
|
131
|
+
"issues": [
|
|
132
|
+
{
|
|
133
|
+
"severity": issue.severity,
|
|
134
|
+
"category": issue.category,
|
|
135
|
+
"message": issue.message
|
|
136
|
+
}
|
|
137
|
+
for issue in issues
|
|
138
|
+
],
|
|
139
|
+
"features_found": features_found
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def validate_fastapi_app(code: str) -> Dict:
|
|
144
|
+
"""
|
|
145
|
+
Validate FastAPI application.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
code: FastAPI application code
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Validation result with issues and recommendations
|
|
152
|
+
"""
|
|
153
|
+
issues: List[FrameworkIssue] = []
|
|
154
|
+
features_found = {}
|
|
155
|
+
|
|
156
|
+
# Check for FastAPI initialization
|
|
157
|
+
if not re.search(WebFrameworkValidatorTool.FASTAPI_PATTERNS["app_initialization"], code):
|
|
158
|
+
issues.append(FrameworkIssue(
|
|
159
|
+
severity="error",
|
|
160
|
+
category="initialization",
|
|
161
|
+
message="FastAPI app not initialized with FastAPI() constructor"
|
|
162
|
+
))
|
|
163
|
+
|
|
164
|
+
# Check for routes
|
|
165
|
+
routes = re.findall(r"@app\.(get|post|put|delete|patch)\(", code)
|
|
166
|
+
features_found["routes"] = len(routes)
|
|
167
|
+
|
|
168
|
+
if not routes:
|
|
169
|
+
issues.append(FrameworkIssue(
|
|
170
|
+
severity="warning",
|
|
171
|
+
category="routing",
|
|
172
|
+
message="No routes defined in FastAPI application"
|
|
173
|
+
))
|
|
174
|
+
|
|
175
|
+
# Check for uvicorn
|
|
176
|
+
has_uvicorn = "uvicorn" in code
|
|
177
|
+
features_found["uvicorn"] = has_uvicorn
|
|
178
|
+
|
|
179
|
+
# Check for type hints
|
|
180
|
+
type_hint_patterns = [
|
|
181
|
+
r"def\s+\w+\([^)]*:\s*\w+[^)]*\)", # Function parameters with type hints
|
|
182
|
+
r"->\s*[A-Z]\w+", # Return type hints
|
|
183
|
+
]
|
|
184
|
+
has_type_hints = any(re.search(pattern, code) for pattern in type_hint_patterns)
|
|
185
|
+
features_found["type_hints"] = has_type_hints
|
|
186
|
+
|
|
187
|
+
# Check for response models
|
|
188
|
+
has_response_models = "response_model=" in code
|
|
189
|
+
features_found["response_models"] = has_response_models
|
|
190
|
+
|
|
191
|
+
if not has_response_models:
|
|
192
|
+
issues.append(FrameworkIssue(
|
|
193
|
+
severity="info",
|
|
194
|
+
category="best_practice",
|
|
195
|
+
message="Consider using response_model for better API documentation"
|
|
196
|
+
))
|
|
197
|
+
|
|
198
|
+
# Check for CORS
|
|
199
|
+
has_cors = "CORSMiddleware" in code or "cors" in code.lower()
|
|
200
|
+
features_found["cors"] = has_cors
|
|
201
|
+
|
|
202
|
+
# Check for dependency injection
|
|
203
|
+
has_dependencies = "Depends(" in code
|
|
204
|
+
features_found["dependency_injection"] = has_dependencies
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"framework": "fastapi",
|
|
208
|
+
"valid": len([i for i in issues if i.severity == "error"]) == 0,
|
|
209
|
+
"issues": [
|
|
210
|
+
{
|
|
211
|
+
"severity": issue.severity,
|
|
212
|
+
"category": issue.category,
|
|
213
|
+
"message": issue.message
|
|
214
|
+
}
|
|
215
|
+
for issue in issues
|
|
216
|
+
],
|
|
217
|
+
"features_found": features_found
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def validate_routing(code: str, framework: str) -> Dict:
|
|
222
|
+
"""
|
|
223
|
+
Validate route definitions.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
code: Application code
|
|
227
|
+
framework: Web framework name
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Routing validation result
|
|
231
|
+
"""
|
|
232
|
+
issues = []
|
|
233
|
+
routes = []
|
|
234
|
+
|
|
235
|
+
if framework == "flask":
|
|
236
|
+
# Extract Flask routes
|
|
237
|
+
pattern = r"@app\.route\(['\"]([^'\"]+)['\"]\s*(?:,\s*methods=\[([^\]]+)\])?\)"
|
|
238
|
+
for match in re.finditer(pattern, code):
|
|
239
|
+
path = match.group(1)
|
|
240
|
+
methods = match.group(2)
|
|
241
|
+
if methods:
|
|
242
|
+
methods = [m.strip().strip("'\"") for m in methods.split(",")]
|
|
243
|
+
else:
|
|
244
|
+
methods = ["GET"]
|
|
245
|
+
|
|
246
|
+
# Check for common issues
|
|
247
|
+
if path.endswith("/"):
|
|
248
|
+
issues.append(f"Route '{path}' ends with trailing slash")
|
|
249
|
+
|
|
250
|
+
if "//" in path:
|
|
251
|
+
issues.append(f"Route '{path}' has double slashes")
|
|
252
|
+
|
|
253
|
+
routes.append({
|
|
254
|
+
"path": path,
|
|
255
|
+
"methods": methods
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
elif framework == "fastapi":
|
|
259
|
+
# Extract FastAPI routes
|
|
260
|
+
pattern = r"@app\.(get|post|put|delete|patch)\(['\"]([^'\"]+)['\"]\)"
|
|
261
|
+
for match in re.finditer(pattern, code):
|
|
262
|
+
method = match.group(1).upper()
|
|
263
|
+
path = match.group(2)
|
|
264
|
+
|
|
265
|
+
# Check for common issues
|
|
266
|
+
if not path.startswith("/"):
|
|
267
|
+
issues.append(f"Route '{path}' should start with /")
|
|
268
|
+
|
|
269
|
+
if path.endswith("/") and path != "/":
|
|
270
|
+
issues.append(f"Route '{path}' ends with trailing slash")
|
|
271
|
+
|
|
272
|
+
routes.append({
|
|
273
|
+
"path": path,
|
|
274
|
+
"method": method
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"framework": framework,
|
|
279
|
+
"routes_found": len(routes),
|
|
280
|
+
"routes": routes,
|
|
281
|
+
"issues": issues,
|
|
282
|
+
"valid": len(issues) == 0
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def validate_middleware(code: str, framework: str) -> Dict:
|
|
287
|
+
"""
|
|
288
|
+
Validate middleware configuration.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
code: Application code
|
|
292
|
+
framework: Web framework name
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Middleware validation result
|
|
296
|
+
"""
|
|
297
|
+
middleware = []
|
|
298
|
+
issues = []
|
|
299
|
+
|
|
300
|
+
if framework == "flask":
|
|
301
|
+
# Check for common middleware patterns
|
|
302
|
+
middleware_patterns = {
|
|
303
|
+
"session": r"from\s+flask_session|Session\(",
|
|
304
|
+
"cors": r"from\s+flask_cors|CORS\(",
|
|
305
|
+
"caching": r"from\s+flask_caching|Cache\(",
|
|
306
|
+
"authentication": r"from\s+flask_login|LoginManager",
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for name, pattern in middleware_patterns.items():
|
|
310
|
+
if re.search(pattern, code):
|
|
311
|
+
middleware.append(name)
|
|
312
|
+
|
|
313
|
+
elif framework == "fastapi":
|
|
314
|
+
# Check for FastAPI middleware
|
|
315
|
+
middleware_patterns = {
|
|
316
|
+
"cors": r"CORSMiddleware",
|
|
317
|
+
"gzip": r"GZipMiddleware",
|
|
318
|
+
"trusted_host": r"TrustedHostMiddleware",
|
|
319
|
+
"http_exception": r"HTTPException",
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for name, pattern in middleware_patterns.items():
|
|
323
|
+
if re.search(pattern, code):
|
|
324
|
+
middleware.append(name)
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"framework": framework,
|
|
328
|
+
"middleware": middleware,
|
|
329
|
+
"middleware_count": len(middleware),
|
|
330
|
+
"issues": issues,
|
|
331
|
+
"valid": True
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@staticmethod
|
|
335
|
+
def validate_full_app(code: str) -> Dict:
|
|
336
|
+
"""
|
|
337
|
+
Perform full validation of a web application.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
code: Application code
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Comprehensive validation result
|
|
344
|
+
"""
|
|
345
|
+
framework = WebFrameworkValidatorTool.detect_framework(code)
|
|
346
|
+
|
|
347
|
+
if not framework:
|
|
348
|
+
return {
|
|
349
|
+
"valid": False,
|
|
350
|
+
"framework": None,
|
|
351
|
+
"error": "Could not detect web framework",
|
|
352
|
+
"sections": {}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
result = {
|
|
356
|
+
"valid": True,
|
|
357
|
+
"framework": framework,
|
|
358
|
+
"sections": {}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Run framework-specific validation
|
|
362
|
+
if framework == "flask":
|
|
363
|
+
result["sections"]["framework"] = WebFrameworkValidatorTool.validate_flask_app(code)
|
|
364
|
+
elif framework == "fastapi":
|
|
365
|
+
result["sections"]["framework"] = WebFrameworkValidatorTool.validate_fastapi_app(code)
|
|
366
|
+
|
|
367
|
+
# Validate routing
|
|
368
|
+
result["sections"]["routing"] = WebFrameworkValidatorTool.validate_routing(code, framework)
|
|
369
|
+
|
|
370
|
+
# Validate middleware
|
|
371
|
+
result["sections"]["middleware"] = WebFrameworkValidatorTool.validate_middleware(code, framework)
|
|
372
|
+
|
|
373
|
+
# Determine overall validity
|
|
374
|
+
errors = []
|
|
375
|
+
for section in result["sections"].values():
|
|
376
|
+
if isinstance(section, dict) and not section.get("valid", True):
|
|
377
|
+
errors.extend(section.get("issues", []))
|
|
378
|
+
|
|
379
|
+
result["valid"] = len(errors) == 0
|
|
380
|
+
result["total_issues"] = len(errors)
|
|
381
|
+
|
|
382
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/tools/web_tools
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
import arxiv
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# arxiv search tool
|
|
8
|
+
|
|
9
|
+
class ArxivSearchTool:
|
|
10
|
+
"""
|
|
11
|
+
arXiv research paper retrieval tool.
|
|
12
|
+
|
|
13
|
+
Responsibilities:
|
|
14
|
+
- retrieve AI/ML papers
|
|
15
|
+
- gather academic references
|
|
16
|
+
- support advanced technical research
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
max_results: int = 5
|
|
22
|
+
):
|
|
23
|
+
|
|
24
|
+
self.max_results = max_results
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# search papers
|
|
28
|
+
|
|
29
|
+
def search(
|
|
30
|
+
self,
|
|
31
|
+
query: str
|
|
32
|
+
) -> Dict:
|
|
33
|
+
"""
|
|
34
|
+
Search arXiv papers.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
|
|
39
|
+
search = arxiv.Search(
|
|
40
|
+
|
|
41
|
+
query=query,
|
|
42
|
+
|
|
43
|
+
max_results=self.max_results,
|
|
44
|
+
|
|
45
|
+
sort_by=arxiv.SortCriterion.Relevance
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
results = []
|
|
49
|
+
|
|
50
|
+
for paper in search.results():
|
|
51
|
+
|
|
52
|
+
results.append({
|
|
53
|
+
|
|
54
|
+
"title": paper.title,
|
|
55
|
+
|
|
56
|
+
"authors": [
|
|
57
|
+
author.name
|
|
58
|
+
for author in paper.authors
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
"summary": paper.summary,
|
|
62
|
+
|
|
63
|
+
"published": str(
|
|
64
|
+
paper.published
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
"pdf_url": paper.pdf_url,
|
|
68
|
+
|
|
69
|
+
"entry_id": paper.entry_id
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
|
|
74
|
+
"success": True,
|
|
75
|
+
|
|
76
|
+
"query": query,
|
|
77
|
+
|
|
78
|
+
"papers": results
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logging.getLogger(__name__).error("ArxivSearchTool.search failed: %s", e, exc_info=True)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
|
|
86
|
+
"success": False,
|
|
87
|
+
|
|
88
|
+
"query": query,
|
|
89
|
+
|
|
90
|
+
"error": str(e),
|
|
91
|
+
|
|
92
|
+
"papers": []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
#global helper for simplified search usage
|
|
97
|
+
|
|
98
|
+
def arxiv_search(
|
|
99
|
+
query: str,
|
|
100
|
+
max_results: int = 5
|
|
101
|
+
) -> Dict:
|
|
102
|
+
"""
|
|
103
|
+
Simple arXiv helper.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
tool = ArxivSearchTool(
|
|
107
|
+
max_results=max_results
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return tool.search(query)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from serpapi import GoogleSearch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# serpapi configuration
|
|
9
|
+
|
|
10
|
+
SERPAPI_API_KEY = os.getenv(
|
|
11
|
+
"SERPAPI_API_KEY"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# serpapi search tool
|
|
16
|
+
|
|
17
|
+
class SerpAPISearchTool:
|
|
18
|
+
"""
|
|
19
|
+
Google Search tool using SerpAPI.
|
|
20
|
+
|
|
21
|
+
Responsibilities:
|
|
22
|
+
- retrieve Google search results
|
|
23
|
+
- fetch technical articles
|
|
24
|
+
- gather implementation references
|
|
25
|
+
- support researcher agent
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
num_results: int = 5
|
|
31
|
+
):
|
|
32
|
+
|
|
33
|
+
self.num_results = num_results
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# search
|
|
37
|
+
|
|
38
|
+
def search(
|
|
39
|
+
self,
|
|
40
|
+
query: str
|
|
41
|
+
) -> Dict:
|
|
42
|
+
"""
|
|
43
|
+
Execute Google search.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
|
|
48
|
+
params = {
|
|
49
|
+
|
|
50
|
+
"engine": "google",
|
|
51
|
+
|
|
52
|
+
"q": query,
|
|
53
|
+
|
|
54
|
+
"api_key": SERPAPI_API_KEY,
|
|
55
|
+
|
|
56
|
+
"num": self.num_results
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
search = GoogleSearch(params)
|
|
60
|
+
|
|
61
|
+
results = search.get_dict()
|
|
62
|
+
|
|
63
|
+
organic_results = results.get(
|
|
64
|
+
"organic_results",
|
|
65
|
+
[]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
|
|
70
|
+
"success": True,
|
|
71
|
+
|
|
72
|
+
"query": query,
|
|
73
|
+
|
|
74
|
+
"results": organic_results
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logging.getLogger(__name__).error("SerpAPISearch.search failed: %s", e, exc_info=True)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
|
|
82
|
+
"success": False,
|
|
83
|
+
|
|
84
|
+
"query": query,
|
|
85
|
+
|
|
86
|
+
"error": str(e),
|
|
87
|
+
|
|
88
|
+
"results": []
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
#global helper for simplified search usage
|
|
93
|
+
|
|
94
|
+
def serpapi_search(
|
|
95
|
+
query: str,
|
|
96
|
+
num_results: int = 5
|
|
97
|
+
) -> Dict:
|
|
98
|
+
"""
|
|
99
|
+
Simple SerpAPI helper.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
tool = SerpAPISearchTool(
|
|
103
|
+
num_results=num_results
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return tool.search(query)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from tavily import TavilyClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
#tavily search tool
|
|
9
|
+
|
|
10
|
+
class TavilySearchTool:
|
|
11
|
+
"""
|
|
12
|
+
Tavily-powered web search tool.
|
|
13
|
+
|
|
14
|
+
Responsibilities:
|
|
15
|
+
- perform AI-optimized web search
|
|
16
|
+
- retrieve technical documentation
|
|
17
|
+
- gather implementation resources
|
|
18
|
+
- support research workflows
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
max_results: int = 5
|
|
24
|
+
):
|
|
25
|
+
|
|
26
|
+
self.max_results = max_results
|
|
27
|
+
api_key = os.getenv("TAVILY_API_KEY")
|
|
28
|
+
if not api_key:
|
|
29
|
+
raise EnvironmentError("TAVILY_API_KEY is not set.")
|
|
30
|
+
self.client = TavilyClient(api_key=api_key)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#search
|
|
34
|
+
|
|
35
|
+
def search(
|
|
36
|
+
self,
|
|
37
|
+
query: str,
|
|
38
|
+
search_depth: str = "advanced"
|
|
39
|
+
) -> Dict:
|
|
40
|
+
"""
|
|
41
|
+
Execute Tavily web search.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
|
|
46
|
+
response = self.client.search(
|
|
47
|
+
|
|
48
|
+
query=query,
|
|
49
|
+
|
|
50
|
+
search_depth=search_depth,
|
|
51
|
+
|
|
52
|
+
max_results=self.max_results
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
|
|
57
|
+
"success": True,
|
|
58
|
+
|
|
59
|
+
"query": query,
|
|
60
|
+
|
|
61
|
+
"results": response.get(
|
|
62
|
+
"results",
|
|
63
|
+
[]
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logging.getLogger(__name__).error("TavilySearchTool.search failed: %s", e, exc_info=True)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
|
|
72
|
+
"success": False,
|
|
73
|
+
|
|
74
|
+
"query": query,
|
|
75
|
+
|
|
76
|
+
"error": str(e),
|
|
77
|
+
|
|
78
|
+
"results": []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# helper function for simplified search usage
|
|
83
|
+
|
|
84
|
+
def tavily_search(
|
|
85
|
+
query: str,
|
|
86
|
+
max_results: int = 5
|
|
87
|
+
) -> Dict:
|
|
88
|
+
"""
|
|
89
|
+
Simple Tavily search helper.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
tool = TavilySearchTool(
|
|
93
|
+
max_results=max_results
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return tool.search(query)
|
acra/ui/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""UI components for acra."""
|
|
2
|
+
|
|
3
|
+
from .banner import welcome_banner
|
|
4
|
+
from .components import render_panel, render_message
|
|
5
|
+
from .shell import launch_shell
|
|
6
|
+
from .theme import get_theme
|
|
7
|
+
|
|
8
|
+
__all__ = ["welcome_banner", "render_panel", "render_message", "launch_shell", "get_theme"]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|