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,108 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from acra.config import GENERATED_PROJECT_DIR, PROJECT_ROOT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# file writer tool
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _resolve_allowed_path(filepath: str) -> Path:
|
|
14
|
+
target = Path(filepath).expanduser().resolve()
|
|
15
|
+
allowed_roots = [
|
|
16
|
+
Path(PROJECT_ROOT).resolve(),
|
|
17
|
+
Path(GENERATED_PROJECT_DIR).resolve(),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
if not any(target == root or target.is_relative_to(root) for root in allowed_roots):
|
|
21
|
+
raise ValueError(f"Refusing to write outside allowed roots: {filepath}")
|
|
22
|
+
|
|
23
|
+
return target
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FileWriterTool:
|
|
27
|
+
"""
|
|
28
|
+
File writing utility.
|
|
29
|
+
|
|
30
|
+
Responsibilities:
|
|
31
|
+
- create files
|
|
32
|
+
- update files
|
|
33
|
+
- create directories
|
|
34
|
+
- support generated projects
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# write file
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def write_file(
|
|
42
|
+
filepath: str,
|
|
43
|
+
content: str
|
|
44
|
+
) -> Dict:
|
|
45
|
+
"""
|
|
46
|
+
Write content to file.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
safe_path = _resolve_allowed_path(filepath)
|
|
51
|
+
|
|
52
|
+
safe_path.parent.mkdir(
|
|
53
|
+
parents=True,
|
|
54
|
+
exist_ok=True
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
with open(
|
|
58
|
+
safe_path,
|
|
59
|
+
"w",
|
|
60
|
+
encoding="utf-8"
|
|
61
|
+
) as f:
|
|
62
|
+
|
|
63
|
+
f.write(content)
|
|
64
|
+
|
|
65
|
+
logger.info(f"File written: {filepath} ({len(content)} bytes)")
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
|
|
69
|
+
"success": True,
|
|
70
|
+
|
|
71
|
+
"filepath": str(safe_path)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Failed to write file {filepath}: {e}", exc_info=True)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
|
|
79
|
+
"success": False,
|
|
80
|
+
|
|
81
|
+
"filepath": filepath,
|
|
82
|
+
|
|
83
|
+
"error": str(e)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# write multiple files
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def write_files(
|
|
91
|
+
files: Dict[str, str]
|
|
92
|
+
) -> Dict:
|
|
93
|
+
"""
|
|
94
|
+
Write multiple files.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
results = {}
|
|
98
|
+
|
|
99
|
+
for filepath, content in files.items():
|
|
100
|
+
|
|
101
|
+
results[filepath] = (
|
|
102
|
+
FileWriterTool.write_file(
|
|
103
|
+
filepath=filepath,
|
|
104
|
+
content=content
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return results
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import io
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# python repl tool
|
|
8
|
+
|
|
9
|
+
class PythonREPLTool:
|
|
10
|
+
"""
|
|
11
|
+
Dynamic Python execution tool.
|
|
12
|
+
|
|
13
|
+
Responsibilities:
|
|
14
|
+
- execute Python snippets
|
|
15
|
+
- capture stdout/stderr
|
|
16
|
+
- support debugging workflows
|
|
17
|
+
- provide runtime experimentation
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# execute tool
|
|
25
|
+
|
|
26
|
+
def execute(
|
|
27
|
+
self,
|
|
28
|
+
code: str
|
|
29
|
+
) -> Dict:
|
|
30
|
+
"""
|
|
31
|
+
Execute Python code safely with fresh globals for each execution.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
fresh_globals = {}
|
|
35
|
+
stdout_buffer = io.StringIO()
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
|
|
39
|
+
with contextlib.redirect_stdout(
|
|
40
|
+
stdout_buffer
|
|
41
|
+
):
|
|
42
|
+
|
|
43
|
+
exec(code, fresh_globals)
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
|
|
47
|
+
"success": True,
|
|
48
|
+
|
|
49
|
+
"stdout": (
|
|
50
|
+
stdout_buffer.getvalue()
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
"error": None
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
except Exception:
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
|
|
60
|
+
"success": False,
|
|
61
|
+
|
|
62
|
+
"stdout": (
|
|
63
|
+
stdout_buffer.getvalue()
|
|
64
|
+
),
|
|
65
|
+
|
|
66
|
+
"error": traceback.format_exc()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# global helper
|
|
71
|
+
|
|
72
|
+
python_repl = PythonREPLTool()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# terminal runner tool
|
|
6
|
+
|
|
7
|
+
class TerminalRunnerTool:
|
|
8
|
+
"""
|
|
9
|
+
Terminal command execution tool.
|
|
10
|
+
|
|
11
|
+
Responsibilities:
|
|
12
|
+
- execute shell commands
|
|
13
|
+
- capture stdout/stderr
|
|
14
|
+
- support workflow automation
|
|
15
|
+
- assist execution/debugging
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# run command
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def run_command(
|
|
23
|
+
command: str,
|
|
24
|
+
cwd: Optional[str] = None,
|
|
25
|
+
timeout: int = 60
|
|
26
|
+
) -> Dict:
|
|
27
|
+
"""
|
|
28
|
+
Execute terminal command.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
|
|
35
|
+
command,
|
|
36
|
+
|
|
37
|
+
shell=True,
|
|
38
|
+
|
|
39
|
+
capture_output=True,
|
|
40
|
+
|
|
41
|
+
text=True,
|
|
42
|
+
|
|
43
|
+
cwd=cwd,
|
|
44
|
+
|
|
45
|
+
timeout=timeout
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
success = (
|
|
49
|
+
result.returncode == 0
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
|
|
54
|
+
"success": success,
|
|
55
|
+
|
|
56
|
+
"command": command,
|
|
57
|
+
|
|
58
|
+
"stdout": result.stdout,
|
|
59
|
+
|
|
60
|
+
"stderr": result.stderr,
|
|
61
|
+
|
|
62
|
+
"return_code": (
|
|
63
|
+
result.returncode
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
except subprocess.TimeoutExpired:
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
|
|
71
|
+
"success": False,
|
|
72
|
+
|
|
73
|
+
"command": command,
|
|
74
|
+
|
|
75
|
+
"stdout": "",
|
|
76
|
+
|
|
77
|
+
"stderr": (
|
|
78
|
+
"Command timed out."
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
|
|
86
|
+
"success": False,
|
|
87
|
+
|
|
88
|
+
"command": command,
|
|
89
|
+
|
|
90
|
+
"stdout": "",
|
|
91
|
+
|
|
92
|
+
"stderr": str(e)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# global helper
|
|
97
|
+
|
|
98
|
+
terminal_runner = TerminalRunnerTool()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/tools/github_tools
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# commit generator tool
|
|
5
|
+
|
|
6
|
+
class CommitMessageGenerator:
|
|
7
|
+
"""
|
|
8
|
+
Git commit message generator.
|
|
9
|
+
|
|
10
|
+
Responsibilities:
|
|
11
|
+
- generate meaningful commit messages
|
|
12
|
+
- summarize code changes
|
|
13
|
+
- support workflow automation
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# generate commit msg
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def generate_commit_message(
|
|
21
|
+
changed_files: List[str],
|
|
22
|
+
change_summary: str,
|
|
23
|
+
commit_type: str = "feat"
|
|
24
|
+
) -> Dict:
|
|
25
|
+
"""
|
|
26
|
+
Generate standardized commit message.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
|
|
31
|
+
short_summary = (
|
|
32
|
+
change_summary.strip()
|
|
33
|
+
.replace("\n", " ")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if len(short_summary) > 72:
|
|
37
|
+
|
|
38
|
+
short_summary = (
|
|
39
|
+
short_summary[:72] + "..."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
commit_message = (
|
|
43
|
+
f"{commit_type}: "
|
|
44
|
+
f"{short_summary}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
detailed_description = {
|
|
48
|
+
|
|
49
|
+
"commit_message": commit_message,
|
|
50
|
+
|
|
51
|
+
"changed_files": changed_files,
|
|
52
|
+
|
|
53
|
+
"summary": change_summary
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
|
|
58
|
+
"success": True,
|
|
59
|
+
|
|
60
|
+
"commit": detailed_description
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
|
|
67
|
+
"success": False,
|
|
68
|
+
|
|
69
|
+
"error": str(e)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# global helper
|
|
74
|
+
|
|
75
|
+
def generate_commit_message(
|
|
76
|
+
changed_files: List[str],
|
|
77
|
+
change_summary: str,
|
|
78
|
+
commit_type: str = "feat"
|
|
79
|
+
) -> Dict:
|
|
80
|
+
"""
|
|
81
|
+
Simple commit generator helper.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
CommitMessageGenerator
|
|
86
|
+
.generate_commit_message(
|
|
87
|
+
changed_files=changed_files,
|
|
88
|
+
change_summary=change_summary,
|
|
89
|
+
commit_type=commit_type
|
|
90
|
+
)
|
|
91
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from github import Github
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# github client
|
|
8
|
+
|
|
9
|
+
GITHUB_TOKEN = os.getenv(
|
|
10
|
+
"GITHUB_TOKEN"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
github_client = Github(GITHUB_TOKEN)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# github search tool
|
|
17
|
+
|
|
18
|
+
class GitHubSearchTool:
|
|
19
|
+
"""
|
|
20
|
+
GitHub repository search tool.
|
|
21
|
+
|
|
22
|
+
Responsibilities:
|
|
23
|
+
- search repositories
|
|
24
|
+
- retrieve implementation references
|
|
25
|
+
- support technical research
|
|
26
|
+
- discover open-source patterns
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
max_results: int = 5
|
|
32
|
+
):
|
|
33
|
+
|
|
34
|
+
self.max_results = max_results
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# search repo
|
|
38
|
+
|
|
39
|
+
def search_repositories(
|
|
40
|
+
self,
|
|
41
|
+
query: str
|
|
42
|
+
) -> Dict:
|
|
43
|
+
"""
|
|
44
|
+
Search GitHub repositories.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
|
|
49
|
+
repositories = (
|
|
50
|
+
github_client.search_repositories(
|
|
51
|
+
query=query
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
results = []
|
|
56
|
+
|
|
57
|
+
for repo in repositories[
|
|
58
|
+
:self.max_results
|
|
59
|
+
]:
|
|
60
|
+
|
|
61
|
+
results.append({
|
|
62
|
+
|
|
63
|
+
"name": repo.full_name,
|
|
64
|
+
|
|
65
|
+
"description": repo.description,
|
|
66
|
+
|
|
67
|
+
"stars": repo.stargazers_count,
|
|
68
|
+
|
|
69
|
+
"language": repo.language,
|
|
70
|
+
|
|
71
|
+
"url": repo.html_url,
|
|
72
|
+
|
|
73
|
+
"topics": repo.get_topics()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
|
|
78
|
+
"success": True,
|
|
79
|
+
|
|
80
|
+
"query": query,
|
|
81
|
+
|
|
82
|
+
"repositories": results
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
|
|
89
|
+
"success": False,
|
|
90
|
+
|
|
91
|
+
"query": query,
|
|
92
|
+
|
|
93
|
+
"error": str(e),
|
|
94
|
+
|
|
95
|
+
"repositories": []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# global helper
|
|
100
|
+
|
|
101
|
+
def github_search(
|
|
102
|
+
query: str,
|
|
103
|
+
max_results: int = 5
|
|
104
|
+
) -> Dict:
|
|
105
|
+
"""
|
|
106
|
+
Simple GitHub repository search helper.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
tool = GitHubSearchTool(
|
|
110
|
+
max_results=max_results
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return tool.search_repositories(query)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from github import Github
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# github client
|
|
8
|
+
|
|
9
|
+
GITHUB_TOKEN = os.getenv(
|
|
10
|
+
"GITHUB_TOKEN"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
github_client = Github(GITHUB_TOKEN)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# repo analyzer
|
|
17
|
+
|
|
18
|
+
class RepoAnalyzerTool:
|
|
19
|
+
"""
|
|
20
|
+
GitHub repository analysis tool.
|
|
21
|
+
|
|
22
|
+
Responsibilities:
|
|
23
|
+
- inspect repository structure
|
|
24
|
+
- analyze technologies
|
|
25
|
+
- retrieve important files
|
|
26
|
+
- support implementation research
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# analyze repo
|
|
31
|
+
|
|
32
|
+
def analyze_repository(
|
|
33
|
+
self,
|
|
34
|
+
repo_name: str
|
|
35
|
+
) -> Dict:
|
|
36
|
+
"""
|
|
37
|
+
Analyze GitHub repository.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
|
|
42
|
+
repo = github_client.get_repo(
|
|
43
|
+
repo_name
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
contents = repo.get_contents("")
|
|
47
|
+
|
|
48
|
+
file_structure = []
|
|
49
|
+
|
|
50
|
+
important_files = []
|
|
51
|
+
|
|
52
|
+
while contents:
|
|
53
|
+
|
|
54
|
+
file_content = contents.pop(0)
|
|
55
|
+
|
|
56
|
+
if file_content.type == "dir":
|
|
57
|
+
|
|
58
|
+
contents.extend(
|
|
59
|
+
repo.get_contents(
|
|
60
|
+
file_content.path
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
|
|
66
|
+
file_structure.append(
|
|
67
|
+
file_content.path
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if any(
|
|
71
|
+
|
|
72
|
+
keyword in file_content.path.lower()
|
|
73
|
+
|
|
74
|
+
for keyword in [
|
|
75
|
+
|
|
76
|
+
"readme",
|
|
77
|
+
"requirements",
|
|
78
|
+
"dockerfile",
|
|
79
|
+
"package.json",
|
|
80
|
+
"main.py",
|
|
81
|
+
"app.py"
|
|
82
|
+
]
|
|
83
|
+
):
|
|
84
|
+
|
|
85
|
+
important_files.append({
|
|
86
|
+
|
|
87
|
+
"path": file_content.path,
|
|
88
|
+
|
|
89
|
+
"download_url": (
|
|
90
|
+
file_content.download_url
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
|
|
96
|
+
"success": True,
|
|
97
|
+
|
|
98
|
+
"repository": repo.full_name,
|
|
99
|
+
|
|
100
|
+
"description": repo.description,
|
|
101
|
+
|
|
102
|
+
"stars": repo.stargazers_count,
|
|
103
|
+
|
|
104
|
+
"language": repo.language,
|
|
105
|
+
|
|
106
|
+
"topics": repo.get_topics(),
|
|
107
|
+
|
|
108
|
+
"file_count": len(file_structure),
|
|
109
|
+
|
|
110
|
+
"important_files": important_files,
|
|
111
|
+
|
|
112
|
+
"file_structure": file_structure[:100]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
|
|
119
|
+
"success": False,
|
|
120
|
+
|
|
121
|
+
"repository": repo_name,
|
|
122
|
+
|
|
123
|
+
"error": str(e)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# global helper
|
|
128
|
+
|
|
129
|
+
def analyze_repository(
|
|
130
|
+
repo_name: str
|
|
131
|
+
) -> Dict:
|
|
132
|
+
"""
|
|
133
|
+
Simple repository analyzer helper.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
tool = RepoAnalyzerTool()
|
|
137
|
+
|
|
138
|
+
return tool.analyze_repository(repo_name)
|