onecoder 0.0.2__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.
- onecoder/agent.py +95 -0
- onecoder/agentic_tool_search/__init__.py +0 -0
- onecoder/agentic_tool_search/dynamic_tool_search.py +64 -0
- onecoder/agentic_tool_search/registry.py +33 -0
- onecoder/agents/__init__.py +7 -0
- onecoder/agents/documentation_agent.py +12 -0
- onecoder/agents/file_reader_agent.py +19 -0
- onecoder/agents/file_writer_agent.py +19 -0
- onecoder/agents/orchestrator_agent.py +51 -0
- onecoder/agents/refactoring_agent.py +12 -0
- onecoder/agents/research_agent.py +31 -0
- onecoder/agents/task_suggestion_agent.py +88 -0
- onecoder/alignment.py +236 -0
- onecoder/api.py +162 -0
- onecoder/api_client.py +112 -0
- onecoder/backends/base.py +22 -0
- onecoder/backends/local_tui.py +65 -0
- onecoder/blackboard.py +102 -0
- onecoder/cli.py +108 -0
- onecoder/commands/__init__.py +1 -0
- onecoder/commands/auth.py +78 -0
- onecoder/commands/ci.py +29 -0
- onecoder/commands/delegate.py +557 -0
- onecoder/commands/doctor.py +40 -0
- onecoder/commands/issue.py +136 -0
- onecoder/commands/logs.py +45 -0
- onecoder/commands/project.py +270 -0
- onecoder/commands/server.py +170 -0
- onecoder/config_manager.py +87 -0
- onecoder/constants.py +9 -0
- onecoder/diagnostics/__init__.py +2 -0
- onecoder/diagnostics/env_scan.py +207 -0
- onecoder/discovery.py +101 -0
- onecoder/distillation.py +236 -0
- onecoder/evaluation/__init__.py +1 -0
- onecoder/evaluation/ttu.py +176 -0
- onecoder/governance/__init__.py +0 -0
- onecoder/governance/probllm.py +91 -0
- onecoder/hooks.py +74 -0
- onecoder/ipc_auth.py +200 -0
- onecoder/issues.py +188 -0
- onecoder/jules_client.py +343 -0
- onecoder/knowledge.py +106 -0
- onecoder/llm.py +61 -0
- onecoder/logger.py +42 -0
- onecoder/metrics.py +129 -0
- onecoder/models/delegation.py +46 -0
- onecoder/onboarding.py +264 -0
- onecoder/review.py +233 -0
- onecoder/services/delegation_service.py +209 -0
- onecoder/services/validation_service.py +104 -0
- onecoder/sessions.py +186 -0
- onecoder/sprint_collector.py +165 -0
- onecoder/sync.py +167 -0
- onecoder/tmux.py +86 -0
- onecoder/tools/__init__.py +10 -0
- onecoder/tools/executor.py +53 -0
- onecoder/tools/external_tools.py +106 -0
- onecoder/tools/file_tools.py +77 -0
- onecoder/tools/interface.py +25 -0
- onecoder/tools/jules_tools.py +122 -0
- onecoder/tools/kit_tools.py +122 -0
- onecoder/tools/registry.py +32 -0
- onecoder/tui/__init__.py +5 -0
- onecoder/tui/app.py +263 -0
- onecoder/tui/commands.py +150 -0
- onecoder/tui/widgets.py +92 -0
- onecoder/worktree.py +186 -0
- onecoder-0.0.2.dist-info/METADATA +17 -0
- onecoder-0.0.2.dist-info/RECORD +73 -0
- onecoder-0.0.2.dist-info/WHEEL +5 -0
- onecoder-0.0.2.dist-info/entry_points.txt +2 -0
- onecoder-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# onecoder/tools/external_tools.py
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import os
|
|
5
|
+
from ..governance.probllm import ProbLLMGuardian
|
|
6
|
+
|
|
7
|
+
def shell_executor_tool(command: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Executes a shell command and returns its output (stdout and stderr).
|
|
10
|
+
Use this for running general system commands, testing, or querying tools.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
command: The full shell command to execute.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The command output or an error message.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
# ProbLLM Governance Check
|
|
20
|
+
# Resolve path to governance.yaml relative to repository root
|
|
21
|
+
# onecoder-cli/onecoder/tools/external_tools.py -> ../../../governance.yaml
|
|
22
|
+
gov_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "governance.yaml"))
|
|
23
|
+
|
|
24
|
+
# Instantiate Guardian on demand
|
|
25
|
+
local_guardian = ProbLLMGuardian(gov_path)
|
|
26
|
+
|
|
27
|
+
is_safe, message = local_guardian.validate_tool_execution("shell_execute", {"command": command})
|
|
28
|
+
if not is_safe:
|
|
29
|
+
return f"GOVERNANCE BLOCK: {message}. Please verify 'governance.yaml' policies."
|
|
30
|
+
|
|
31
|
+
# Run the command
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
command,
|
|
34
|
+
shell=True,
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
timeout=30,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
output = ""
|
|
41
|
+
if result.stdout:
|
|
42
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
|
43
|
+
if result.stderr:
|
|
44
|
+
output += f"STDERR:\n{result.stderr}\n"
|
|
45
|
+
|
|
46
|
+
raw_response = output or f"Command exited with code {result.returncode}"
|
|
47
|
+
|
|
48
|
+
# ProbLLM Output Validation
|
|
49
|
+
is_safe, message = local_guardian.validate_output(raw_response)
|
|
50
|
+
if not is_safe:
|
|
51
|
+
return f"GOVERNANCE BLOCK: {message}"
|
|
52
|
+
|
|
53
|
+
if not output and result.returncode == 0:
|
|
54
|
+
return "Command executed successfully with no output."
|
|
55
|
+
|
|
56
|
+
return raw_response
|
|
57
|
+
|
|
58
|
+
except subprocess.TimeoutExpired:
|
|
59
|
+
return "Error: Command timed out after 30 seconds."
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return f"An error occurred while executing the command: {e}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def gemini_tool(query: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Executes a Gemini CLI command ('gemini ask') for general queries.
|
|
67
|
+
Note: For image generation (nanobanana), inform the user that it must be done
|
|
68
|
+
interactively within the Gemini TUI.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
query: The query or instruction to pass to 'gemini ask'.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The result from Gemini CLI or an error/guidance message.
|
|
75
|
+
"""
|
|
76
|
+
# Check if the query is about image generation
|
|
77
|
+
image_keywords = ["image", "generate", "draw", "picture", "nanobanana"]
|
|
78
|
+
if any(keyword in query.lower() for keyword in image_keywords):
|
|
79
|
+
return (
|
|
80
|
+
"To generate images with 'nanobanana', you must use the Gemini TUI. "
|
|
81
|
+
"Run 'gemini' in your terminal and use the interactive prompt there."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# Construct the 'gemini ask' command
|
|
86
|
+
# We escape single quotes in the query to avoid shell injection issues
|
|
87
|
+
escaped_query = query.replace("'", "'\\''")
|
|
88
|
+
command = f"gemini ask '{escaped_query}'"
|
|
89
|
+
|
|
90
|
+
result = subprocess.run(
|
|
91
|
+
command,
|
|
92
|
+
shell=True,
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
timeout=60, # Gemini might take longer
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if result.returncode == 0:
|
|
99
|
+
return result.stdout
|
|
100
|
+
else:
|
|
101
|
+
return f"Error running gemini: {result.stderr}"
|
|
102
|
+
|
|
103
|
+
except subprocess.TimeoutExpired:
|
|
104
|
+
return "Error: Gemini command timed out."
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return f"An error occurred while running Gemini: {e}"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# onecoder/tools/file_tools.py
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def write_file_tool(filepath: str, content: str) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Writes content to a file at the given filepath.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
filepath: The path to the file.
|
|
10
|
+
content: The content to write to the file.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
A success message or an error message if the file cannot be written.
|
|
14
|
+
"""
|
|
15
|
+
try:
|
|
16
|
+
with open(filepath, "w") as f:
|
|
17
|
+
f.write(content)
|
|
18
|
+
|
|
19
|
+
# Trigger hooks
|
|
20
|
+
try:
|
|
21
|
+
from onecoder.hooks import hooks_manager
|
|
22
|
+
hooks_manager.on_file_edit(filepath)
|
|
23
|
+
except ImportError:
|
|
24
|
+
pass # Ignore if hooks module is not available or has circular import
|
|
25
|
+
except Exception as e:
|
|
26
|
+
print(f"Warning: Failed to trigger hooks: {e}")
|
|
27
|
+
|
|
28
|
+
return f"Successfully wrote content to {filepath}"
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return f"An error occurred while writing to the file: {e}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_file_tool(filepath: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Reads the contents of a file at the given filepath.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
filepath: The path to the file.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The content of the file, or an error message if the file cannot be read.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
with open(filepath, "r") as f:
|
|
45
|
+
return f.read()
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
return f"Error: File not found at {filepath}"
|
|
48
|
+
except Exception as e:
|
|
49
|
+
return f"An error occurred while reading the file: {e}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def list_directory_tool(directory: str = ".") -> str:
|
|
53
|
+
"""
|
|
54
|
+
Lists the contents of a directory.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
directory: The path to the directory to list.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A string containing the directory listing, or an error message if the directory cannot be read.
|
|
61
|
+
"""
|
|
62
|
+
import os
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
items = os.listdir(directory)
|
|
66
|
+
files = [
|
|
67
|
+
item for item in items if os.path.isfile(os.path.join(directory, item))
|
|
68
|
+
]
|
|
69
|
+
dirs = [item for item in items if os.path.isdir(os.path.join(directory, item))]
|
|
70
|
+
result = f"Directory: {directory}\n"
|
|
71
|
+
if dirs:
|
|
72
|
+
result += f"Directories: {', '.join(dirs)}\n"
|
|
73
|
+
if files:
|
|
74
|
+
result += f"Files: {', '.join(files)}"
|
|
75
|
+
return result
|
|
76
|
+
except Exception as e:
|
|
77
|
+
return f"An error occurred while listing directory {directory}: {e}"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# onecoder/tools/interface.py
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
class BaseTool(BaseModel):
|
|
7
|
+
"""Base class for all tools in the system."""
|
|
8
|
+
name: str
|
|
9
|
+
description: str
|
|
10
|
+
func: Callable
|
|
11
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
12
|
+
|
|
13
|
+
def execute(self, **kwargs) -> Any:
|
|
14
|
+
"""Execute the tool function with the given arguments."""
|
|
15
|
+
return self.func(**kwargs)
|
|
16
|
+
|
|
17
|
+
async def execute_async(self, **kwargs) -> Any:
|
|
18
|
+
"""Execute the tool function asynchronously if it is a coroutine."""
|
|
19
|
+
from ..metrics import TTUMetrics
|
|
20
|
+
TTUMetrics.get_instance().track_first_tool_call()
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
if asyncio.iscoroutinefunction(self.func):
|
|
24
|
+
return await self.func(**kwargs)
|
|
25
|
+
return self.func(**kwargs)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Jules API integration tool for OneCoder.
|
|
2
|
+
|
|
3
|
+
This module provides tool functions that wrap the JulesAPIClient for use
|
|
4
|
+
in the ADK tool registry and backward compatibility with existing tests.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import requests
|
|
9
|
+
from typing import Union
|
|
10
|
+
from ..jules_client import JulesAPIClient, JulesAPIError, JulesAuthError, JulesNotFoundError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def jules_delegate_tool(prompt: str, source: Union[str, None] = None) -> str:
|
|
14
|
+
"""Delegate a coding task to Google Jules.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
prompt: The coding task description
|
|
18
|
+
source: GitHub source (e.g., 'sources/github/owner/repo').
|
|
19
|
+
Defaults to JULES_SOURCE env var.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Status message with session ID and initial response
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
# Initialize client
|
|
26
|
+
client = JulesAPIClient()
|
|
27
|
+
|
|
28
|
+
# Create session
|
|
29
|
+
session = client.create_session(prompt, source=source)
|
|
30
|
+
|
|
31
|
+
# Get initial activities
|
|
32
|
+
try:
|
|
33
|
+
activities = client.list_activities(session.id, page_size=5)
|
|
34
|
+
except JulesNotFoundError:
|
|
35
|
+
# Activities might not be available yet
|
|
36
|
+
activities = []
|
|
37
|
+
|
|
38
|
+
# Format response
|
|
39
|
+
result = f"✓ Jules session created: {session.id}\n"
|
|
40
|
+
result += f"Task: {prompt}\n"
|
|
41
|
+
result += f"Source: {source or os.environ.get('JULES_SOURCE')}\n\n"
|
|
42
|
+
|
|
43
|
+
if activities:
|
|
44
|
+
result += "Initial Activities:\n"
|
|
45
|
+
for activity in activities[:3]:
|
|
46
|
+
if activity.activity_type == "plan_generated":
|
|
47
|
+
plan = activity.data.get("planGenerated", {}).get("plan", {})
|
|
48
|
+
steps = len(plan.get("steps", []))
|
|
49
|
+
result += f"- Plan generated with {steps} steps\n"
|
|
50
|
+
elif activity.activity_type == "progress_updated":
|
|
51
|
+
progress = activity.data.get("progressUpdated", {})
|
|
52
|
+
result += f"- {progress.get('title', 'Progress update')}\n"
|
|
53
|
+
|
|
54
|
+
result += f"\nMonitor progress at: https://jules.google.com/sessions/{session.id}"
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
except JulesAuthError as e:
|
|
58
|
+
return f"Error: JULES_API_KEY environment variable not set"
|
|
59
|
+
except JulesAPIError as e:
|
|
60
|
+
return f"Error: {str(e)}"
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return f"Error: {str(e)}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def jules_status_tool(session_id: str) -> str:
|
|
66
|
+
"""Check the status of a Jules session.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
session_id: The Jules session ID
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Current status and recent activities
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
# Initialize client
|
|
76
|
+
client = JulesAPIClient()
|
|
77
|
+
|
|
78
|
+
# Get session info
|
|
79
|
+
session = client.get_session(session_id)
|
|
80
|
+
|
|
81
|
+
# Get recent activities
|
|
82
|
+
try:
|
|
83
|
+
activities = client.list_activities(session_id, page_size=10)
|
|
84
|
+
except JulesNotFoundError:
|
|
85
|
+
activities = []
|
|
86
|
+
|
|
87
|
+
# Format response
|
|
88
|
+
result = f"Session: {session_id}\n"
|
|
89
|
+
result += f"Title: {session.title or 'N/A'}\n"
|
|
90
|
+
result += f"Prompt: {session.prompt or 'N/A'}\n\n"
|
|
91
|
+
|
|
92
|
+
# Check for PR output
|
|
93
|
+
pr_output = client.detect_pr_output(session_id)
|
|
94
|
+
if pr_output:
|
|
95
|
+
result += "Outputs:\n"
|
|
96
|
+
result += f"- PR: {pr_output['url']}\n"
|
|
97
|
+
result += f" Title: {pr_output['title']}\n"
|
|
98
|
+
|
|
99
|
+
# Show recent activities
|
|
100
|
+
if activities:
|
|
101
|
+
result += "\nRecent Activities:\n"
|
|
102
|
+
for activity in activities[:5]:
|
|
103
|
+
if activity.activity_type == "plan_generated":
|
|
104
|
+
result += f"- [{activity.originator}] Plan generated\n"
|
|
105
|
+
elif activity.activity_type == "progress_updated":
|
|
106
|
+
progress = activity.data.get("progressUpdated", {})
|
|
107
|
+
result += f"- [{activity.originator}] {progress.get('title', 'Progress')}\n"
|
|
108
|
+
elif activity.activity_type == "session_completed":
|
|
109
|
+
result += f"- [{activity.originator}] Session completed ✓\n"
|
|
110
|
+
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
except JulesNotFoundError:
|
|
114
|
+
return f"Jules session with ID '{session_id}' not found."
|
|
115
|
+
except JulesAuthError:
|
|
116
|
+
return "Error: JULES_API_KEY environment variable not set"
|
|
117
|
+
except JulesAPIError as e:
|
|
118
|
+
return f"Error communicating with Jules API: {str(e)}"
|
|
119
|
+
except requests.exceptions.RequestException as e:
|
|
120
|
+
return f"Error communicating with Jules API: {str(e)}"
|
|
121
|
+
except Exception as e:
|
|
122
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# onecoder/tools/kit_tools.py
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def kit_index_tool(directory: str = ".") -> str:
|
|
8
|
+
"""
|
|
9
|
+
Uses kit to build and return a comprehensive index of the repository.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
directory: The path to the repository directory to index.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A JSON string containing the repository index, or an error message.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
# Run kit index command
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
["kit", "index", "."],
|
|
21
|
+
cwd=directory,
|
|
22
|
+
capture_output=True,
|
|
23
|
+
text=True,
|
|
24
|
+
timeout=30,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if result.returncode == 0:
|
|
28
|
+
# Parse and return the JSON
|
|
29
|
+
try:
|
|
30
|
+
index_data = json.loads(result.stdout)
|
|
31
|
+
return json.dumps(index_data, indent=2)
|
|
32
|
+
except json.JSONDecodeError:
|
|
33
|
+
return (
|
|
34
|
+
f"Kit index completed but output is not valid JSON: {result.stdout}"
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
return f"Error running kit index: {result.stderr}"
|
|
38
|
+
|
|
39
|
+
except subprocess.TimeoutExpired:
|
|
40
|
+
return "Error: kit index command timed out"
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
return "Error: kit command not found. Make sure kit is installed."
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return f"An error occurred while running kit index: {e}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def kit_file_tree_tool(directory: str = ".") -> str:
|
|
48
|
+
"""
|
|
49
|
+
Uses kit to get the file tree structure of a repository.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
directory: The path to the repository directory.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A string containing the file tree structure, or an error message.
|
|
56
|
+
"""
|
|
57
|
+
import tempfile
|
|
58
|
+
import os
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Create a temporary file to store the output
|
|
62
|
+
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.json') as tmp:
|
|
63
|
+
tmp_path = tmp.name
|
|
64
|
+
|
|
65
|
+
result = subprocess.run(
|
|
66
|
+
["kit", "file-tree", directory, "--output", tmp_path],
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
timeout=10
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if result.returncode == 0:
|
|
73
|
+
with open(tmp_path, 'r') as f:
|
|
74
|
+
content = f.read()
|
|
75
|
+
os.remove(tmp_path)
|
|
76
|
+
try:
|
|
77
|
+
return json.loads(content)
|
|
78
|
+
except json.JSONDecodeError:
|
|
79
|
+
return f"Error: Invalid JSON from kit: {content}"
|
|
80
|
+
else:
|
|
81
|
+
if os.path.exists(tmp_path):
|
|
82
|
+
os.remove(tmp_path)
|
|
83
|
+
return f"Error running kit file-tree: {result.stderr}"
|
|
84
|
+
|
|
85
|
+
except subprocess.TimeoutExpired:
|
|
86
|
+
return "Error: kit file-tree command timed out"
|
|
87
|
+
except Exception as e:
|
|
88
|
+
return f"An error occurred while running kit file-tree: {e}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def kit_symbols_tool(directory: str = ".") -> str:
|
|
92
|
+
"""
|
|
93
|
+
Uses kit to extract code symbols from the repository.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
directory: The path to the repository directory.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A JSON string containing code symbols, or an error message.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
result = subprocess.run(
|
|
103
|
+
["kit", "symbols", directory, "--format", "json"],
|
|
104
|
+
cwd=directory,
|
|
105
|
+
capture_output=True,
|
|
106
|
+
text=True,
|
|
107
|
+
timeout=15,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if result.returncode == 0:
|
|
111
|
+
try:
|
|
112
|
+
symbols_data = json.loads(result.stdout)
|
|
113
|
+
return json.dumps(symbols_data, indent=2)
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
return f"Kit symbols completed but output is not valid JSON: {result.stdout}"
|
|
116
|
+
else:
|
|
117
|
+
return f"Error running kit symbols: {result.stderr}"
|
|
118
|
+
|
|
119
|
+
except subprocess.TimeoutExpired:
|
|
120
|
+
return "Error: kit symbols command timed out"
|
|
121
|
+
except Exception as e:
|
|
122
|
+
return f"An error occurred while running kit symbols: {e}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# onecoder/tools/registry.py
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
from .interface import BaseTool
|
|
5
|
+
|
|
6
|
+
class ToolRegistry:
|
|
7
|
+
"""A registry for managing and discovering tools."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._tools: Dict[str, BaseTool] = {}
|
|
11
|
+
|
|
12
|
+
def register(self, tool: BaseTool):
|
|
13
|
+
"""Register a tool in the registry."""
|
|
14
|
+
self._tools[tool.name] = tool
|
|
15
|
+
|
|
16
|
+
def get_tool(self, name: str) -> Optional[BaseTool]:
|
|
17
|
+
"""Retrieve a tool by name."""
|
|
18
|
+
return self._tools.get(name)
|
|
19
|
+
|
|
20
|
+
def list_tools(self) -> List[BaseTool]:
|
|
21
|
+
"""List all registered tools."""
|
|
22
|
+
return list(self._tools.values())
|
|
23
|
+
|
|
24
|
+
def get_tool_descriptions(self) -> str:
|
|
25
|
+
"""Get a formatted string of all tool names and descriptions."""
|
|
26
|
+
descriptions = []
|
|
27
|
+
for tool in self._tools.values():
|
|
28
|
+
descriptions.append(f"- {tool.name}: {tool.description}")
|
|
29
|
+
return "\n".join(descriptions)
|
|
30
|
+
|
|
31
|
+
# Global registry instance
|
|
32
|
+
registry = ToolRegistry()
|