toolplane-python-client 0.1.0__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.
- toolplane/__init__.py +106 -0
- toolplane/common/__init__.py +93 -0
- toolplane/common/base_config.py +129 -0
- toolplane/common/base_connection_manager.py +171 -0
- toolplane/common/base_session_manager.py +321 -0
- toolplane/common/base_tool_manager.py +347 -0
- toolplane/common/constants.py +47 -0
- toolplane/common/utils.py +310 -0
- toolplane/core/__init__.py +67 -0
- toolplane/core/config.py +107 -0
- toolplane/core/connection.py +285 -0
- toolplane/core/errors.py +298 -0
- toolplane/core/machine.py +480 -0
- toolplane/core/request.py +775 -0
- toolplane/core/session.py +332 -0
- toolplane/core/session_context.py +514 -0
- toolplane/core/task.py +130 -0
- toolplane/core/tool.py +329 -0
- toolplane/http_core/__init__.py +37 -0
- toolplane/http_core/http_config.py +97 -0
- toolplane/http_core/http_connection.py +409 -0
- toolplane/http_core/http_machine.py +298 -0
- toolplane/http_core/http_request.py +748 -0
- toolplane/http_core/http_session.py +348 -0
- toolplane/http_core/http_session_context.py +491 -0
- toolplane/http_core/http_task.py +101 -0
- toolplane/http_core/http_tool.py +400 -0
- toolplane/interfaces/__init__.py +27 -0
- toolplane/interfaces/client_interface.py +122 -0
- toolplane/interfaces/connection_interface.py +193 -0
- toolplane/interfaces/event_interface.py +290 -0
- toolplane/interfaces/request_interface.py +439 -0
- toolplane/interfaces/session_interface.py +288 -0
- toolplane/interfaces/tool_interface.py +441 -0
- toolplane/proto/__init__.py +0 -0
- toolplane/proto/service_pb2.py +315 -0
- toolplane/proto/service_pb2_grpc.py +2240 -0
- toolplane/provider_cli.py +268 -0
- toolplane/provider_registry.py +77 -0
- toolplane/provider_runtime.py +302 -0
- toolplane/toolkits/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/create_directory.py +94 -0
- toolplane/toolkits/standalone_tools/create_file.py +124 -0
- toolplane/toolkits/standalone_tools/file_search.py +229 -0
- toolplane/toolkits/standalone_tools/grep_search.py +372 -0
- toolplane/toolkits/standalone_tools/launcher.py +146 -0
- toolplane/toolkits/standalone_tools/list_dir.py +395 -0
- toolplane/toolkits/standalone_tools/read_file.py +346 -0
- toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
- toolplane/toolkits/standalone_tools/run_tests.py +66 -0
- toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
- toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
- toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
- toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
- toolplane/toolkits/swe/__init__.py +35 -0
- toolplane/toolkits/swe/create_directory.py +15 -0
- toolplane/toolkits/swe/create_file.py +15 -0
- toolplane/toolkits/swe/descriptions.py +273 -0
- toolplane/toolkits/swe/execute_bash.py +93 -0
- toolplane/toolkits/swe/file_editor.py +775 -0
- toolplane/toolkits/swe/file_search.py +16 -0
- toolplane/toolkits/swe/finish.py +50 -0
- toolplane/toolkits/swe/grep_search.py +19 -0
- toolplane/toolkits/swe/list_dir.py +407 -0
- toolplane/toolkits/swe/read_file.py +18 -0
- toolplane/toolkits/swe/replace_string_in_file.py +17 -0
- toolplane/toolkits/swe/search.py +260 -0
- toolplane/toolkits/swe/semantic_search.py +20 -0
- toolplane/toolkits/swe/str_replace_editor.py +647 -0
- toolplane/toolkits/swe/submit.py +29 -0
- toolplane/toolkits/swe/swe_toolkit.py +1296 -0
- toolplane/toolplane_client.py +686 -0
- toolplane/toolplane_http_client.py +681 -0
- toolplane/utils/__init__.py +3 -0
- toolplane/utils/schema.py +146 -0
- toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
- toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
- toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
- toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
- toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Description: Fast text search in files using grep-like functionality with advanced features.
|
|
4
|
+
|
|
5
|
+
This tool performs fast text search in files using exact strings or regex patterns.
|
|
6
|
+
It provides context lines, file filtering, and other advanced search options.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
query (string, required): The pattern to search for in files.
|
|
10
|
+
is_regexp (boolean, required): Whether the pattern is a regex.
|
|
11
|
+
include_pattern (string, optional): Search files matching this glob pattern.
|
|
12
|
+
max_results (integer, optional): Maximum number of results to return.
|
|
13
|
+
context_lines (integer, optional): Number of context lines to show around matches.
|
|
14
|
+
ignore_case (boolean, optional): Perform case-insensitive search.
|
|
15
|
+
whole_word (boolean, optional): Match whole words only.
|
|
16
|
+
invert_match (boolean, optional): Show lines that don't match the pattern.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import glob
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, Iterator, List
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def grep_search(
|
|
29
|
+
query: str,
|
|
30
|
+
is_regexp: bool,
|
|
31
|
+
include_pattern: str = None,
|
|
32
|
+
max_results: int = None,
|
|
33
|
+
context_lines: int = 0,
|
|
34
|
+
ignore_case: bool = False,
|
|
35
|
+
whole_word: bool = False,
|
|
36
|
+
invert_match: bool = False,
|
|
37
|
+
base_path: str = None,
|
|
38
|
+
) -> List[Dict[str, Any]]:
|
|
39
|
+
"""
|
|
40
|
+
Fast text search in files using grep-like functionality.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
query: The pattern to search for
|
|
44
|
+
is_regexp: Whether the pattern is a regex
|
|
45
|
+
include_pattern: Search files matching this glob pattern
|
|
46
|
+
max_results: Maximum number of results to return
|
|
47
|
+
context_lines: Number of context lines to show around matches
|
|
48
|
+
ignore_case: Perform case-insensitive search
|
|
49
|
+
whole_word: Match whole words only
|
|
50
|
+
invert_match: Show lines that don't match the pattern
|
|
51
|
+
base_path: Base directory to search from
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of dictionaries containing search results
|
|
55
|
+
"""
|
|
56
|
+
if base_path is None:
|
|
57
|
+
base_path = os.getcwd()
|
|
58
|
+
|
|
59
|
+
# Prepare the search pattern
|
|
60
|
+
if is_regexp:
|
|
61
|
+
pattern = query
|
|
62
|
+
else:
|
|
63
|
+
pattern = re.escape(query)
|
|
64
|
+
|
|
65
|
+
if whole_word:
|
|
66
|
+
pattern = r"\b" + pattern + r"\b"
|
|
67
|
+
|
|
68
|
+
flags = re.IGNORECASE if ignore_case else 0
|
|
69
|
+
try:
|
|
70
|
+
compiled_pattern = re.compile(pattern, flags)
|
|
71
|
+
except re.error as e:
|
|
72
|
+
print(f"Invalid regex pattern: {e}", file=sys.stderr)
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
# Get list of files to search
|
|
76
|
+
if include_pattern:
|
|
77
|
+
files_to_search = get_files_by_pattern(include_pattern, base_path)
|
|
78
|
+
else:
|
|
79
|
+
files_to_search = get_all_text_files(base_path)
|
|
80
|
+
|
|
81
|
+
results = []
|
|
82
|
+
total_matches = 0
|
|
83
|
+
|
|
84
|
+
for file_path in files_to_search:
|
|
85
|
+
if max_results and total_matches >= max_results:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
file_results = search_file(
|
|
90
|
+
file_path, compiled_pattern, context_lines, invert_match
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if file_results["matches"]:
|
|
94
|
+
results.append(file_results)
|
|
95
|
+
total_matches += len(file_results["matches"])
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
# Skip files that can't be read
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_files_by_pattern(pattern: str, base_path: str) -> List[str]:
|
|
105
|
+
"""Get list of files matching the glob pattern.
|
|
106
|
+
|
|
107
|
+
Globs against base_path directly instead of os.chdir: changing the
|
|
108
|
+
process working directory is global state and races with every other
|
|
109
|
+
thread in the provider runtime.
|
|
110
|
+
"""
|
|
111
|
+
matches = glob.glob(str(Path(base_path) / pattern), recursive=True)
|
|
112
|
+
files = []
|
|
113
|
+
|
|
114
|
+
for match in matches:
|
|
115
|
+
path = Path(match)
|
|
116
|
+
if path.is_file():
|
|
117
|
+
files.append(str(path.resolve()))
|
|
118
|
+
|
|
119
|
+
return files
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_all_text_files(base_path: str) -> List[str]:
|
|
123
|
+
"""Get all text files in the directory tree."""
|
|
124
|
+
text_extensions = {
|
|
125
|
+
".txt",
|
|
126
|
+
".py",
|
|
127
|
+
".js",
|
|
128
|
+
".ts",
|
|
129
|
+
".jsx",
|
|
130
|
+
".tsx",
|
|
131
|
+
".java",
|
|
132
|
+
".cpp",
|
|
133
|
+
".c",
|
|
134
|
+
".h",
|
|
135
|
+
".cs",
|
|
136
|
+
".php",
|
|
137
|
+
".rb",
|
|
138
|
+
".go",
|
|
139
|
+
".rs",
|
|
140
|
+
".swift",
|
|
141
|
+
".kt",
|
|
142
|
+
".scala",
|
|
143
|
+
".r",
|
|
144
|
+
".sql",
|
|
145
|
+
".html",
|
|
146
|
+
".htm",
|
|
147
|
+
".css",
|
|
148
|
+
".scss",
|
|
149
|
+
".sass",
|
|
150
|
+
".less",
|
|
151
|
+
".xml",
|
|
152
|
+
".json",
|
|
153
|
+
".yaml",
|
|
154
|
+
".yml",
|
|
155
|
+
".toml",
|
|
156
|
+
".ini",
|
|
157
|
+
".cfg",
|
|
158
|
+
".conf",
|
|
159
|
+
".md",
|
|
160
|
+
".rst",
|
|
161
|
+
".tex",
|
|
162
|
+
".sh",
|
|
163
|
+
".bash",
|
|
164
|
+
".zsh",
|
|
165
|
+
".fish",
|
|
166
|
+
".ps1",
|
|
167
|
+
".bat",
|
|
168
|
+
".dockerfile",
|
|
169
|
+
".makefile",
|
|
170
|
+
".cmake",
|
|
171
|
+
".gradle",
|
|
172
|
+
".properties",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
files = []
|
|
176
|
+
for root, dirs, file_names in os.walk(base_path):
|
|
177
|
+
# Skip hidden directories
|
|
178
|
+
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
|
179
|
+
|
|
180
|
+
for filename in file_names:
|
|
181
|
+
if filename.startswith("."):
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
path = Path(root) / filename
|
|
185
|
+
|
|
186
|
+
# Check extension
|
|
187
|
+
if path.suffix.lower() in text_extensions:
|
|
188
|
+
files.append(str(path.resolve()))
|
|
189
|
+
# Check if it's a text file by reading first few bytes
|
|
190
|
+
elif not path.suffix and is_text_file(path):
|
|
191
|
+
files.append(str(path.resolve()))
|
|
192
|
+
|
|
193
|
+
return files
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def is_text_file(file_path: Path) -> bool:
|
|
197
|
+
"""Check if a file is a text file by examining its content."""
|
|
198
|
+
try:
|
|
199
|
+
with open(file_path, "rb") as f:
|
|
200
|
+
chunk = f.read(1024)
|
|
201
|
+
|
|
202
|
+
# Check for null bytes (binary files usually contain them)
|
|
203
|
+
if b"\x00" in chunk:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
# Try to decode as UTF-8
|
|
207
|
+
try:
|
|
208
|
+
chunk.decode("utf-8")
|
|
209
|
+
return True
|
|
210
|
+
except UnicodeDecodeError:
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
except (IOError, OSError):
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def search_file(
|
|
218
|
+
file_path: str, pattern: re.Pattern, context_lines: int, invert_match: bool
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""Search for pattern in a single file."""
|
|
221
|
+
try:
|
|
222
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
223
|
+
lines = f.readlines()
|
|
224
|
+
except Exception as e:
|
|
225
|
+
return {"file": file_path, "error": str(e), "matches": []}
|
|
226
|
+
|
|
227
|
+
matches = []
|
|
228
|
+
|
|
229
|
+
for line_num, line in enumerate(lines, 1):
|
|
230
|
+
line = line.rstrip("\n\r")
|
|
231
|
+
|
|
232
|
+
if invert_match:
|
|
233
|
+
match = not pattern.search(line)
|
|
234
|
+
else:
|
|
235
|
+
match = pattern.search(line)
|
|
236
|
+
|
|
237
|
+
if match:
|
|
238
|
+
# Get context lines
|
|
239
|
+
start_line = max(0, line_num - context_lines - 1)
|
|
240
|
+
end_line = min(len(lines), line_num + context_lines)
|
|
241
|
+
|
|
242
|
+
context = []
|
|
243
|
+
for i in range(start_line, end_line):
|
|
244
|
+
context_line = lines[i].rstrip("\n\r")
|
|
245
|
+
context.append(
|
|
246
|
+
{
|
|
247
|
+
"line_number": i + 1,
|
|
248
|
+
"content": context_line,
|
|
249
|
+
"is_match": i + 1 == line_num,
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
matches.append(
|
|
254
|
+
{
|
|
255
|
+
"line_number": line_num,
|
|
256
|
+
"content": line,
|
|
257
|
+
"context": context if context_lines > 0 else None,
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return {"file": file_path, "matches": matches, "total_matches": len(matches)}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main():
|
|
265
|
+
parser = argparse.ArgumentParser(
|
|
266
|
+
description="Fast text search in files using grep-like functionality with advanced features."
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument("query", type=str, help="The pattern to search for in files")
|
|
269
|
+
parser.add_argument(
|
|
270
|
+
"--is_regexp",
|
|
271
|
+
action="store_true",
|
|
272
|
+
default=False,
|
|
273
|
+
help="Whether the pattern is a regex",
|
|
274
|
+
)
|
|
275
|
+
parser.add_argument(
|
|
276
|
+
"--include_pattern", type=str, help="Search files matching this glob pattern"
|
|
277
|
+
)
|
|
278
|
+
parser.add_argument(
|
|
279
|
+
"--max_results", type=int, help="Maximum number of results to return"
|
|
280
|
+
)
|
|
281
|
+
parser.add_argument(
|
|
282
|
+
"--context_lines",
|
|
283
|
+
type=int,
|
|
284
|
+
default=0,
|
|
285
|
+
help="Number of context lines to show around matches",
|
|
286
|
+
)
|
|
287
|
+
parser.add_argument(
|
|
288
|
+
"--ignore_case",
|
|
289
|
+
action="store_true",
|
|
290
|
+
default=False,
|
|
291
|
+
help="Perform case-insensitive search",
|
|
292
|
+
)
|
|
293
|
+
parser.add_argument(
|
|
294
|
+
"--whole_word",
|
|
295
|
+
action="store_true",
|
|
296
|
+
default=False,
|
|
297
|
+
help="Match whole words only",
|
|
298
|
+
)
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--invert_match",
|
|
301
|
+
action="store_true",
|
|
302
|
+
default=False,
|
|
303
|
+
help="Show lines that don't match the pattern",
|
|
304
|
+
)
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
"--base_path",
|
|
307
|
+
type=str,
|
|
308
|
+
help="Base directory to search from (default: current directory)",
|
|
309
|
+
)
|
|
310
|
+
parser.add_argument(
|
|
311
|
+
"--output_format",
|
|
312
|
+
choices=["default", "json", "count"],
|
|
313
|
+
default="default",
|
|
314
|
+
help="Output format (default: default)",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
args = parser.parse_args()
|
|
318
|
+
|
|
319
|
+
results = grep_search(
|
|
320
|
+
args.query,
|
|
321
|
+
args.is_regexp,
|
|
322
|
+
include_pattern=args.include_pattern,
|
|
323
|
+
max_results=args.max_results,
|
|
324
|
+
context_lines=args.context_lines,
|
|
325
|
+
ignore_case=args.ignore_case,
|
|
326
|
+
whole_word=args.whole_word,
|
|
327
|
+
invert_match=args.invert_match,
|
|
328
|
+
base_path=args.base_path,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if not results:
|
|
332
|
+
print(f"No matches found for pattern: {args.query}")
|
|
333
|
+
sys.exit(0)
|
|
334
|
+
|
|
335
|
+
if args.output_format == "json":
|
|
336
|
+
import json
|
|
337
|
+
|
|
338
|
+
print(json.dumps(results, indent=2))
|
|
339
|
+
|
|
340
|
+
elif args.output_format == "count":
|
|
341
|
+
total_matches = sum(r["total_matches"] for r in results)
|
|
342
|
+
print(f"Total matches: {total_matches}")
|
|
343
|
+
print(f"Files with matches: {len(results)}")
|
|
344
|
+
|
|
345
|
+
for result in results:
|
|
346
|
+
print(f"{result['file']}: {result['total_matches']} matches")
|
|
347
|
+
|
|
348
|
+
else: # default format
|
|
349
|
+
total_matches = sum(r["total_matches"] for r in results)
|
|
350
|
+
print(f"Found {total_matches} matches in {len(results)} files")
|
|
351
|
+
print("=" * 60)
|
|
352
|
+
|
|
353
|
+
for result in results:
|
|
354
|
+
if "error" in result:
|
|
355
|
+
print(f"Error in {result['file']}: {result['error']}")
|
|
356
|
+
continue
|
|
357
|
+
|
|
358
|
+
print(f"\nFile: {result['file']}")
|
|
359
|
+
print(f"Matches: {result['total_matches']}")
|
|
360
|
+
print("-" * 40)
|
|
361
|
+
|
|
362
|
+
for match in result["matches"]:
|
|
363
|
+
if args.context_lines > 0 and match["context"]:
|
|
364
|
+
for ctx in match["context"]:
|
|
365
|
+
prefix = ">" if ctx["is_match"] else " "
|
|
366
|
+
print(f"{prefix}{ctx['line_number']:4d}: {ctx['content']}")
|
|
367
|
+
else:
|
|
368
|
+
print(f"{match['line_number']:4d}: {match['content']}")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if __name__ == "__main__":
|
|
372
|
+
main()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Standalone Tools Launcher
|
|
4
|
+
|
|
5
|
+
This script helps you discover and run the available standalone tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_available_tools():
|
|
16
|
+
"""Get list of available tools in the current directory."""
|
|
17
|
+
tools = []
|
|
18
|
+
current_dir = Path(__file__).parent
|
|
19
|
+
|
|
20
|
+
for file in current_dir.glob("*.py"):
|
|
21
|
+
if file.name not in ["launcher.py", "__init__.py"]:
|
|
22
|
+
tools.append(file.name)
|
|
23
|
+
|
|
24
|
+
return sorted(tools)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_tool_description(tool_name):
|
|
28
|
+
"""Extract description from tool's docstring."""
|
|
29
|
+
try:
|
|
30
|
+
tool_path = Path(__file__).parent / tool_name
|
|
31
|
+
with open(tool_path, "r", encoding="utf-8") as f:
|
|
32
|
+
content = f.read()
|
|
33
|
+
|
|
34
|
+
# Extract description from docstring
|
|
35
|
+
if '"""' in content:
|
|
36
|
+
docstring = content.split('"""')[1]
|
|
37
|
+
if "Description:" in docstring:
|
|
38
|
+
desc_line = docstring.split("Description:")[1].split("\n")[0]
|
|
39
|
+
return desc_line.strip()
|
|
40
|
+
|
|
41
|
+
return "No description available"
|
|
42
|
+
except:
|
|
43
|
+
return "No description available"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def list_tools():
|
|
47
|
+
"""List all available tools with their descriptions."""
|
|
48
|
+
print("Available Standalone Tools:")
|
|
49
|
+
print("=" * 50)
|
|
50
|
+
|
|
51
|
+
tools = get_available_tools()
|
|
52
|
+
for tool in tools:
|
|
53
|
+
description = get_tool_description(tool)
|
|
54
|
+
print(f"{tool:<30} - {description}")
|
|
55
|
+
|
|
56
|
+
print(f"\nTotal: {len(tools)} tools available")
|
|
57
|
+
print(
|
|
58
|
+
"\nUse 'python launcher.py run <tool_name> --help' to see tool-specific options"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_tool(tool_name, args):
|
|
63
|
+
"""Run a specific tool with the given arguments."""
|
|
64
|
+
tools = get_available_tools()
|
|
65
|
+
|
|
66
|
+
if tool_name not in tools:
|
|
67
|
+
print(f"Error: Tool '{tool_name}' not found.")
|
|
68
|
+
print("Available tools:")
|
|
69
|
+
for tool in tools:
|
|
70
|
+
print(f" - {tool}")
|
|
71
|
+
return 1
|
|
72
|
+
|
|
73
|
+
tool_path = Path(__file__).parent / tool_name
|
|
74
|
+
|
|
75
|
+
# Execute the tool
|
|
76
|
+
try:
|
|
77
|
+
cmd = [sys.executable, str(tool_path)] + args
|
|
78
|
+
result = subprocess.run(cmd, check=False)
|
|
79
|
+
return result.returncode
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"Error running tool '{tool_name}': {e}")
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def search_tools(keyword):
|
|
86
|
+
"""Search for tools by keyword in name or description."""
|
|
87
|
+
print(f"Searching for tools containing '{keyword}':")
|
|
88
|
+
print("=" * 50)
|
|
89
|
+
|
|
90
|
+
tools = get_available_tools()
|
|
91
|
+
found = []
|
|
92
|
+
|
|
93
|
+
for tool in tools:
|
|
94
|
+
description = get_tool_description(tool)
|
|
95
|
+
if keyword.lower() in tool.lower() or keyword.lower() in description.lower():
|
|
96
|
+
found.append((tool, description))
|
|
97
|
+
|
|
98
|
+
if found:
|
|
99
|
+
for tool, description in found:
|
|
100
|
+
print(f"{tool:<30} - {description}")
|
|
101
|
+
print(f"\nFound {len(found)} matching tools")
|
|
102
|
+
else:
|
|
103
|
+
print("No tools found matching the keyword.")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
parser = argparse.ArgumentParser(
|
|
108
|
+
description="Launcher for standalone development tools",
|
|
109
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
110
|
+
epilog="""
|
|
111
|
+
Examples:
|
|
112
|
+
python launcher.py list # List all available tools
|
|
113
|
+
python launcher.py search grep # Search for tools containing 'grep'
|
|
114
|
+
python launcher.py run file_search.py "*.py" # Run file_search.py with arguments
|
|
115
|
+
python launcher.py run read_file.py --help # Show help for read_file.py
|
|
116
|
+
""",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
120
|
+
|
|
121
|
+
# List command
|
|
122
|
+
list_parser = subparsers.add_parser("list", help="List all available tools")
|
|
123
|
+
|
|
124
|
+
# Search command
|
|
125
|
+
search_parser = subparsers.add_parser("search", help="Search for tools by keyword")
|
|
126
|
+
search_parser.add_argument("keyword", help="Keyword to search for")
|
|
127
|
+
|
|
128
|
+
# Run command
|
|
129
|
+
run_parser = subparsers.add_parser("run", help="Run a specific tool")
|
|
130
|
+
run_parser.add_argument("tool", help="Name of the tool to run")
|
|
131
|
+
run_parser.add_argument("args", nargs="*", help="Arguments to pass to the tool")
|
|
132
|
+
|
|
133
|
+
args = parser.parse_args()
|
|
134
|
+
|
|
135
|
+
if args.command == "list" or args.command is None:
|
|
136
|
+
list_tools()
|
|
137
|
+
elif args.command == "search":
|
|
138
|
+
search_tools(args.keyword)
|
|
139
|
+
elif args.command == "run":
|
|
140
|
+
return run_tool(args.tool, args.args)
|
|
141
|
+
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
sys.exit(main())
|