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.
@@ -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))