supyagent 0.1.0__py3-none-any.whl → 0.2.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.
- supyagent/cli/main.py +251 -1
- supyagent/core/__init__.py +3 -0
- supyagent/core/config.py +352 -0
- supyagent/default_tools/__init__.py +74 -0
- supyagent/default_tools/files.py +439 -0
- supyagent/default_tools/shell.py +217 -0
- {supyagent-0.1.0.dist-info → supyagent-0.2.1.dist-info}/METADATA +95 -32
- {supyagent-0.1.0.dist-info → supyagent-0.2.1.dist-info}/RECORD +11 -7
- {supyagent-0.1.0.dist-info → supyagent-0.2.1.dist-info}/WHEEL +0 -0
- {supyagent-0.1.0.dist-info → supyagent-0.2.1.dist-info}/entry_points.txt +0 -0
- {supyagent-0.1.0.dist-info → supyagent-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default supypowers tools bundled with supyagent.
|
|
3
|
+
|
|
4
|
+
These tools are copied to the user's project when running `supyagent init`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Path to the bundled default tools
|
|
11
|
+
TOOLS_DIR = Path(__file__).parent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_bundled_tools() -> list[Path]:
|
|
15
|
+
"""Get list of bundled tool files."""
|
|
16
|
+
return [f for f in TOOLS_DIR.glob("*.py") if f.name != "__init__.py"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def install_default_tools(target_dir: Path | str = "supypowers") -> int:
|
|
20
|
+
"""
|
|
21
|
+
Install default tools to a target directory.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
target_dir: Directory to install tools to (default: supypowers/)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Number of files installed
|
|
28
|
+
"""
|
|
29
|
+
target = Path(target_dir)
|
|
30
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
|
|
32
|
+
installed = 0
|
|
33
|
+
for tool_file in get_bundled_tools():
|
|
34
|
+
dest = target / tool_file.name
|
|
35
|
+
if not dest.exists():
|
|
36
|
+
shutil.copy(tool_file, dest)
|
|
37
|
+
installed += 1
|
|
38
|
+
|
|
39
|
+
# Create __init__.py if not exists
|
|
40
|
+
init_file = target / "__init__.py"
|
|
41
|
+
if not init_file.exists():
|
|
42
|
+
init_file.write_text('"""Supypowers tools for this project."""\n')
|
|
43
|
+
installed += 1
|
|
44
|
+
|
|
45
|
+
return installed
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def list_default_tools() -> list[dict]:
|
|
49
|
+
"""
|
|
50
|
+
List available default tools.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of tool info dicts
|
|
54
|
+
"""
|
|
55
|
+
tools = []
|
|
56
|
+
for tool_file in get_bundled_tools():
|
|
57
|
+
# Read first docstring
|
|
58
|
+
content = tool_file.read_text()
|
|
59
|
+
description = ""
|
|
60
|
+
if '"""' in content:
|
|
61
|
+
start = content.find('"""') + 3
|
|
62
|
+
end = content.find('"""', start)
|
|
63
|
+
if end > start:
|
|
64
|
+
description = content[start:end].strip().split("\n")[0]
|
|
65
|
+
|
|
66
|
+
tools.append(
|
|
67
|
+
{
|
|
68
|
+
"name": tool_file.stem,
|
|
69
|
+
"file": tool_file.name,
|
|
70
|
+
"description": description,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return tools
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# dependencies = ["pydantic"]
|
|
3
|
+
# ///
|
|
4
|
+
"""
|
|
5
|
+
File system operation tools.
|
|
6
|
+
|
|
7
|
+
Allows agents to read, write, and manage files and directories.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# Read File
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ReadFileInput(BaseModel):
|
|
24
|
+
"""Input for read_file function."""
|
|
25
|
+
|
|
26
|
+
path: str = Field(description="Path to the file to read")
|
|
27
|
+
encoding: str = Field(default="utf-8", description="File encoding")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReadFileOutput(BaseModel):
|
|
31
|
+
"""Output for read_file function."""
|
|
32
|
+
|
|
33
|
+
ok: bool
|
|
34
|
+
content: Optional[str] = None
|
|
35
|
+
size: Optional[int] = None
|
|
36
|
+
path: Optional[str] = None
|
|
37
|
+
error: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_file(input: ReadFileInput) -> ReadFileOutput:
|
|
41
|
+
"""
|
|
42
|
+
Read the contents of a file.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
>>> read_file({"path": "README.md"})
|
|
46
|
+
>>> read_file({"path": "data.txt", "encoding": "latin-1"})
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
path = os.path.expanduser(input.path)
|
|
50
|
+
p = Path(path)
|
|
51
|
+
|
|
52
|
+
if not p.exists():
|
|
53
|
+
return ReadFileOutput(ok=False, error=f"File not found: {path}")
|
|
54
|
+
|
|
55
|
+
if not p.is_file():
|
|
56
|
+
return ReadFileOutput(ok=False, error=f"Not a file: {path}")
|
|
57
|
+
|
|
58
|
+
size = p.stat().st_size
|
|
59
|
+
if size > 10 * 1024 * 1024:
|
|
60
|
+
return ReadFileOutput(
|
|
61
|
+
ok=False,
|
|
62
|
+
error=f"File too large ({size} bytes). Maximum is 10MB.",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
content = p.read_text(encoding=input.encoding)
|
|
66
|
+
return ReadFileOutput(
|
|
67
|
+
ok=True,
|
|
68
|
+
content=content,
|
|
69
|
+
size=size,
|
|
70
|
+
path=str(p.absolute()),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
except UnicodeDecodeError:
|
|
74
|
+
return ReadFileOutput(
|
|
75
|
+
ok=False,
|
|
76
|
+
error=f"Cannot decode file as {input.encoding}",
|
|
77
|
+
)
|
|
78
|
+
except PermissionError:
|
|
79
|
+
return ReadFileOutput(ok=False, error=f"Permission denied: {input.path}")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return ReadFileOutput(ok=False, error=str(e))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# =============================================================================
|
|
85
|
+
# Write File
|
|
86
|
+
# =============================================================================
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class WriteFileInput(BaseModel):
|
|
90
|
+
"""Input for write_file function."""
|
|
91
|
+
|
|
92
|
+
path: str = Field(description="Path to the file to write")
|
|
93
|
+
content: str = Field(description="Content to write")
|
|
94
|
+
encoding: str = Field(default="utf-8", description="File encoding")
|
|
95
|
+
create_dirs: bool = Field(
|
|
96
|
+
default=True, description="Create parent directories if needed"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class WriteFileOutput(BaseModel):
|
|
101
|
+
"""Output for write_file function."""
|
|
102
|
+
|
|
103
|
+
ok: bool
|
|
104
|
+
path: Optional[str] = None
|
|
105
|
+
size: Optional[int] = None
|
|
106
|
+
error: Optional[str] = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def write_file(input: WriteFileInput) -> WriteFileOutput:
|
|
110
|
+
"""
|
|
111
|
+
Write content to a file.
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
>>> write_file({"path": "output.txt", "content": "Hello, world!"})
|
|
115
|
+
>>> write_file({"path": "data/results.json", "content": '{"status": "ok"}'})
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
path = os.path.expanduser(input.path)
|
|
119
|
+
p = Path(path)
|
|
120
|
+
|
|
121
|
+
if input.create_dirs:
|
|
122
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
|
|
124
|
+
p.write_text(input.content, encoding=input.encoding)
|
|
125
|
+
|
|
126
|
+
return WriteFileOutput(
|
|
127
|
+
ok=True,
|
|
128
|
+
path=str(p.absolute()),
|
|
129
|
+
size=len(input.content.encode(input.encoding)),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
except PermissionError:
|
|
133
|
+
return WriteFileOutput(ok=False, error=f"Permission denied: {input.path}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
return WriteFileOutput(ok=False, error=str(e))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# =============================================================================
|
|
139
|
+
# List Directory
|
|
140
|
+
# =============================================================================
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ListDirectoryInput(BaseModel):
|
|
144
|
+
"""Input for list_directory function."""
|
|
145
|
+
|
|
146
|
+
path: str = Field(default=".", description="Directory path")
|
|
147
|
+
pattern: Optional[str] = Field(
|
|
148
|
+
default=None, description="Optional glob pattern (e.g., '*.py')"
|
|
149
|
+
)
|
|
150
|
+
recursive: bool = Field(default=False, description="List recursively")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class FileInfo(BaseModel):
|
|
154
|
+
"""Information about a single file/directory."""
|
|
155
|
+
|
|
156
|
+
name: str
|
|
157
|
+
path: str
|
|
158
|
+
type: str # "file" or "directory"
|
|
159
|
+
size: Optional[int] = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ListDirectoryOutput(BaseModel):
|
|
163
|
+
"""Output for list_directory function."""
|
|
164
|
+
|
|
165
|
+
ok: bool
|
|
166
|
+
items: List[FileInfo] = []
|
|
167
|
+
count: int = 0
|
|
168
|
+
error: Optional[str] = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def list_directory(input: ListDirectoryInput) -> ListDirectoryOutput:
|
|
172
|
+
"""
|
|
173
|
+
List files and directories.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> list_directory({"path": "."})
|
|
177
|
+
>>> list_directory({"path": "src", "pattern": "*.py"})
|
|
178
|
+
>>> list_directory({"path": ".", "pattern": "*.md", "recursive": True})
|
|
179
|
+
"""
|
|
180
|
+
try:
|
|
181
|
+
path = os.path.expanduser(input.path)
|
|
182
|
+
p = Path(path)
|
|
183
|
+
|
|
184
|
+
if not p.exists():
|
|
185
|
+
return ListDirectoryOutput(
|
|
186
|
+
ok=False, error=f"Directory not found: {path}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if not p.is_dir():
|
|
190
|
+
return ListDirectoryOutput(ok=False, error=f"Not a directory: {path}")
|
|
191
|
+
|
|
192
|
+
items = []
|
|
193
|
+
|
|
194
|
+
if input.pattern:
|
|
195
|
+
matches = p.rglob(input.pattern) if input.recursive else p.glob(input.pattern)
|
|
196
|
+
else:
|
|
197
|
+
matches = p.iterdir()
|
|
198
|
+
|
|
199
|
+
for item in sorted(matches):
|
|
200
|
+
try:
|
|
201
|
+
stat = item.stat()
|
|
202
|
+
items.append(
|
|
203
|
+
FileInfo(
|
|
204
|
+
name=item.name,
|
|
205
|
+
path=str(item),
|
|
206
|
+
type="directory" if item.is_dir() else "file",
|
|
207
|
+
size=stat.st_size if item.is_file() else None,
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
except (PermissionError, OSError):
|
|
211
|
+
pass # Skip inaccessible files
|
|
212
|
+
|
|
213
|
+
return ListDirectoryOutput(ok=True, items=items, count=len(items))
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
return ListDirectoryOutput(ok=False, error=str(e))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# =============================================================================
|
|
220
|
+
# File Info
|
|
221
|
+
# =============================================================================
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class FileInfoInput(BaseModel):
|
|
225
|
+
"""Input for file_info function."""
|
|
226
|
+
|
|
227
|
+
path: str = Field(description="Path to get info for")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class FileInfoOutput(BaseModel):
|
|
231
|
+
"""Output for file_info function."""
|
|
232
|
+
|
|
233
|
+
ok: bool
|
|
234
|
+
path: Optional[str] = None
|
|
235
|
+
name: Optional[str] = None
|
|
236
|
+
type: Optional[str] = None
|
|
237
|
+
size: Optional[int] = None
|
|
238
|
+
extension: Optional[str] = None
|
|
239
|
+
error: Optional[str] = None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def file_info(input: FileInfoInput) -> FileInfoOutput:
|
|
243
|
+
"""
|
|
244
|
+
Get detailed information about a file or directory.
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
>>> file_info({"path": "README.md"})
|
|
248
|
+
"""
|
|
249
|
+
try:
|
|
250
|
+
path = os.path.expanduser(input.path)
|
|
251
|
+
p = Path(path)
|
|
252
|
+
|
|
253
|
+
if not p.exists():
|
|
254
|
+
return FileInfoOutput(ok=False, error=f"Path not found: {path}")
|
|
255
|
+
|
|
256
|
+
stat = p.stat()
|
|
257
|
+
|
|
258
|
+
return FileInfoOutput(
|
|
259
|
+
ok=True,
|
|
260
|
+
path=str(p.absolute()),
|
|
261
|
+
name=p.name,
|
|
262
|
+
type="directory" if p.is_dir() else "file",
|
|
263
|
+
size=stat.st_size,
|
|
264
|
+
extension=p.suffix if p.is_file() else None,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
return FileInfoOutput(ok=False, error=str(e))
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# =============================================================================
|
|
272
|
+
# Delete File
|
|
273
|
+
# =============================================================================
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class DeleteFileInput(BaseModel):
|
|
277
|
+
"""Input for delete_file function."""
|
|
278
|
+
|
|
279
|
+
path: str = Field(description="Path to the file to delete")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class DeleteFileOutput(BaseModel):
|
|
283
|
+
"""Output for delete_file function."""
|
|
284
|
+
|
|
285
|
+
ok: bool
|
|
286
|
+
deleted: Optional[str] = None
|
|
287
|
+
error: Optional[str] = None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def delete_file(input: DeleteFileInput) -> DeleteFileOutput:
|
|
291
|
+
"""
|
|
292
|
+
Delete a file.
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
>>> delete_file({"path": "temp.txt"})
|
|
296
|
+
"""
|
|
297
|
+
try:
|
|
298
|
+
path = os.path.expanduser(input.path)
|
|
299
|
+
p = Path(path)
|
|
300
|
+
|
|
301
|
+
if not p.exists():
|
|
302
|
+
return DeleteFileOutput(ok=False, error=f"File not found: {path}")
|
|
303
|
+
|
|
304
|
+
if p.is_dir():
|
|
305
|
+
return DeleteFileOutput(
|
|
306
|
+
ok=False, error=f"Use delete_directory for directories: {path}"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
p.unlink()
|
|
310
|
+
return DeleteFileOutput(ok=True, deleted=str(p))
|
|
311
|
+
|
|
312
|
+
except Exception as e:
|
|
313
|
+
return DeleteFileOutput(ok=False, error=str(e))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# =============================================================================
|
|
317
|
+
# Create Directory
|
|
318
|
+
# =============================================================================
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class CreateDirectoryInput(BaseModel):
|
|
322
|
+
"""Input for create_directory function."""
|
|
323
|
+
|
|
324
|
+
path: str = Field(description="Directory path to create")
|
|
325
|
+
parents: bool = Field(
|
|
326
|
+
default=True, description="Create parent directories if needed"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class CreateDirectoryOutput(BaseModel):
|
|
331
|
+
"""Output for create_directory function."""
|
|
332
|
+
|
|
333
|
+
ok: bool
|
|
334
|
+
path: Optional[str] = None
|
|
335
|
+
error: Optional[str] = None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def create_directory(input: CreateDirectoryInput) -> CreateDirectoryOutput:
|
|
339
|
+
"""
|
|
340
|
+
Create a directory.
|
|
341
|
+
|
|
342
|
+
Examples:
|
|
343
|
+
>>> create_directory({"path": "new_folder"})
|
|
344
|
+
>>> create_directory({"path": "a/b/c/deep"})
|
|
345
|
+
"""
|
|
346
|
+
try:
|
|
347
|
+
path = os.path.expanduser(input.path)
|
|
348
|
+
p = Path(path)
|
|
349
|
+
|
|
350
|
+
p.mkdir(parents=input.parents, exist_ok=True)
|
|
351
|
+
|
|
352
|
+
return CreateDirectoryOutput(ok=True, path=str(p.absolute()))
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
return CreateDirectoryOutput(ok=False, error=str(e))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# =============================================================================
|
|
359
|
+
# Copy File
|
|
360
|
+
# =============================================================================
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class CopyFileInput(BaseModel):
|
|
364
|
+
"""Input for copy_file function."""
|
|
365
|
+
|
|
366
|
+
source: str = Field(description="Source file path")
|
|
367
|
+
destination: str = Field(description="Destination path")
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class CopyFileOutput(BaseModel):
|
|
371
|
+
"""Output for copy_file function."""
|
|
372
|
+
|
|
373
|
+
ok: bool
|
|
374
|
+
source: Optional[str] = None
|
|
375
|
+
destination: Optional[str] = None
|
|
376
|
+
error: Optional[str] = None
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def copy_file(input: CopyFileInput) -> CopyFileOutput:
|
|
380
|
+
"""
|
|
381
|
+
Copy a file.
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
>>> copy_file({"source": "file.txt", "destination": "backup/file.txt"})
|
|
385
|
+
"""
|
|
386
|
+
try:
|
|
387
|
+
source = os.path.expanduser(input.source)
|
|
388
|
+
destination = os.path.expanduser(input.destination)
|
|
389
|
+
|
|
390
|
+
# Create destination directory if needed
|
|
391
|
+
dest_path = Path(destination)
|
|
392
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
393
|
+
|
|
394
|
+
shutil.copy2(source, destination)
|
|
395
|
+
|
|
396
|
+
return CopyFileOutput(ok=True, source=source, destination=destination)
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
return CopyFileOutput(ok=False, error=str(e))
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# =============================================================================
|
|
403
|
+
# Move File
|
|
404
|
+
# =============================================================================
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class MoveFileInput(BaseModel):
|
|
408
|
+
"""Input for move_file function."""
|
|
409
|
+
|
|
410
|
+
source: str = Field(description="Source path")
|
|
411
|
+
destination: str = Field(description="Destination path")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class MoveFileOutput(BaseModel):
|
|
415
|
+
"""Output for move_file function."""
|
|
416
|
+
|
|
417
|
+
ok: bool
|
|
418
|
+
source: Optional[str] = None
|
|
419
|
+
destination: Optional[str] = None
|
|
420
|
+
error: Optional[str] = None
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def move_file(input: MoveFileInput) -> MoveFileOutput:
|
|
424
|
+
"""
|
|
425
|
+
Move or rename a file or directory.
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
>>> move_file({"source": "old.txt", "destination": "new.txt"})
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
source = os.path.expanduser(input.source)
|
|
432
|
+
destination = os.path.expanduser(input.destination)
|
|
433
|
+
|
|
434
|
+
shutil.move(source, destination)
|
|
435
|
+
|
|
436
|
+
return MoveFileOutput(ok=True, source=source, destination=destination)
|
|
437
|
+
|
|
438
|
+
except Exception as e:
|
|
439
|
+
return MoveFileOutput(ok=False, error=str(e))
|