supyagent 0.2.0__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of supyagent might be problematic. Click here for more details.
- supyagent/cli/main.py +83 -15
- supyagent/core/llm.py +4 -0
- supyagent/core/tools.py +43 -25
- supyagent/default_tools/__init__.py +74 -0
- supyagent/default_tools/files.py +439 -0
- supyagent/default_tools/shell.py +217 -0
- {supyagent-0.2.0.dist-info → supyagent-0.2.2.dist-info}/METADATA +27 -3
- {supyagent-0.2.0.dist-info → supyagent-0.2.2.dist-info}/RECORD +11 -8
- {supyagent-0.2.0.dist-info → supyagent-0.2.2.dist-info}/WHEEL +0 -0
- {supyagent-0.2.0.dist-info → supyagent-0.2.2.dist-info}/entry_points.txt +0 -0
- {supyagent-0.2.0.dist-info → supyagent-0.2.2.dist-info}/licenses/LICENSE +0 -0
supyagent/cli/main.py
CHANGED
|
@@ -21,13 +21,15 @@ from supyagent.core.config import ConfigManager, load_config
|
|
|
21
21
|
from supyagent.core.executor import ExecutionRunner
|
|
22
22
|
from supyagent.core.registry import AgentRegistry
|
|
23
23
|
from supyagent.core.session_manager import SessionManager
|
|
24
|
+
from supyagent.default_tools import install_default_tools, list_default_tools
|
|
24
25
|
from supyagent.models.agent_config import AgentNotFoundError, load_agent_config
|
|
25
26
|
|
|
26
27
|
console = Console()
|
|
28
|
+
console_err = Console(stderr=True)
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@click.group()
|
|
30
|
-
@click.version_option(version="0.2.
|
|
32
|
+
@click.version_option(version="0.2.2", prog_name="supyagent")
|
|
31
33
|
def cli():
|
|
32
34
|
"""
|
|
33
35
|
Supyagent - LLM agents powered by supypowers.
|
|
@@ -37,12 +39,82 @@ def cli():
|
|
|
37
39
|
Quick start:
|
|
38
40
|
|
|
39
41
|
\b
|
|
42
|
+
supyagent init # Set up default tools
|
|
43
|
+
supyagent config set # Configure API keys
|
|
40
44
|
supyagent new myagent # Create an agent
|
|
41
45
|
supyagent chat myagent # Start chatting
|
|
42
46
|
"""
|
|
43
47
|
pass
|
|
44
48
|
|
|
45
49
|
|
|
50
|
+
@cli.command()
|
|
51
|
+
@click.option(
|
|
52
|
+
"--tools-dir",
|
|
53
|
+
"-t",
|
|
54
|
+
default="supypowers",
|
|
55
|
+
help="Directory for tools (default: supypowers/)",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--force",
|
|
59
|
+
"-f",
|
|
60
|
+
is_flag=True,
|
|
61
|
+
help="Overwrite existing files",
|
|
62
|
+
)
|
|
63
|
+
def init(tools_dir: str, force: bool):
|
|
64
|
+
"""
|
|
65
|
+
Initialize supyagent in the current directory.
|
|
66
|
+
|
|
67
|
+
This sets up:
|
|
68
|
+
- Default tools in supypowers/ (shell commands, file operations)
|
|
69
|
+
- agents/ directory for agent configurations
|
|
70
|
+
|
|
71
|
+
\b
|
|
72
|
+
Examples:
|
|
73
|
+
supyagent init
|
|
74
|
+
supyagent init --tools-dir my_tools
|
|
75
|
+
"""
|
|
76
|
+
console.print("[bold]Initializing supyagent...[/bold]")
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
# Create agents directory
|
|
80
|
+
agents_dir = Path("agents")
|
|
81
|
+
if not agents_dir.exists():
|
|
82
|
+
agents_dir.mkdir(parents=True)
|
|
83
|
+
console.print(f" [green]✓[/green] Created {agents_dir}/")
|
|
84
|
+
else:
|
|
85
|
+
console.print(f" [dim]○[/dim] {agents_dir}/ already exists")
|
|
86
|
+
|
|
87
|
+
# Install default tools
|
|
88
|
+
tools_path = Path(tools_dir)
|
|
89
|
+
|
|
90
|
+
if force:
|
|
91
|
+
# Remove and reinstall
|
|
92
|
+
import shutil
|
|
93
|
+
|
|
94
|
+
if tools_path.exists():
|
|
95
|
+
shutil.rmtree(tools_path)
|
|
96
|
+
|
|
97
|
+
if tools_path.exists() and any(tools_path.glob("*.py")):
|
|
98
|
+
console.print(f" [dim]○[/dim] {tools_dir}/ already has tools")
|
|
99
|
+
else:
|
|
100
|
+
count = install_default_tools(tools_path)
|
|
101
|
+
console.print(
|
|
102
|
+
f" [green]✓[/green] Installed {count} default tools to {tools_dir}/"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Show available tools
|
|
106
|
+
console.print()
|
|
107
|
+
console.print("[bold]Available tools:[/bold]")
|
|
108
|
+
for tool in list_default_tools():
|
|
109
|
+
console.print(f" • [cyan]{tool['name']}[/cyan]: {tool['description']}")
|
|
110
|
+
|
|
111
|
+
console.print()
|
|
112
|
+
console.print("[bold]Next steps:[/bold]")
|
|
113
|
+
console.print(" 1. Configure your API key: [cyan]supyagent config set[/cyan]")
|
|
114
|
+
console.print(" 2. Create an agent: [cyan]supyagent new myagent[/cyan]")
|
|
115
|
+
console.print(" 3. Start chatting: [cyan]supyagent chat myagent[/cyan]")
|
|
116
|
+
|
|
117
|
+
|
|
46
118
|
@cli.command()
|
|
47
119
|
@click.argument("name")
|
|
48
120
|
@click.option(
|
|
@@ -636,15 +708,14 @@ def run(
|
|
|
636
708
|
try:
|
|
637
709
|
config = load_agent_config(agent_name)
|
|
638
710
|
except AgentNotFoundError as e:
|
|
639
|
-
|
|
711
|
+
console_err.print(f"[red]Error:[/red] {e}")
|
|
640
712
|
sys.exit(1)
|
|
641
713
|
|
|
642
714
|
# Warn if using interactive agent in execution mode
|
|
643
715
|
if config.type != "execution" and not quiet:
|
|
644
|
-
|
|
716
|
+
console_err.print(
|
|
645
717
|
f"[yellow]Note:[/yellow] '{agent_name}' is an interactive agent. "
|
|
646
|
-
"Consider using 'chat' for interactive use."
|
|
647
|
-
err=True,
|
|
718
|
+
"Consider using 'chat' for interactive use."
|
|
648
719
|
)
|
|
649
720
|
|
|
650
721
|
# Parse secrets
|
|
@@ -659,9 +730,7 @@ def run(
|
|
|
659
730
|
else:
|
|
660
731
|
input_path = Path(input_file)
|
|
661
732
|
if not input_path.exists():
|
|
662
|
-
|
|
663
|
-
f"[red]Error:[/red] File not found: {input_file}", err=True
|
|
664
|
-
)
|
|
733
|
+
console_err.print(f"[red]Error:[/red] File not found: {input_file}")
|
|
665
734
|
sys.exit(1)
|
|
666
735
|
task_content = input_path.read_text().strip()
|
|
667
736
|
elif task:
|
|
@@ -675,22 +744,21 @@ def run(
|
|
|
675
744
|
if not sys.stdin.isatty():
|
|
676
745
|
task_content = sys.stdin.read().strip()
|
|
677
746
|
else:
|
|
678
|
-
|
|
747
|
+
console_err.print(
|
|
679
748
|
"[red]Error:[/red] No task provided. "
|
|
680
|
-
"Use positional argument, --input, or pipe to stdin."
|
|
681
|
-
err=True,
|
|
749
|
+
"Use positional argument, --input, or pipe to stdin."
|
|
682
750
|
)
|
|
683
751
|
sys.exit(1)
|
|
684
752
|
|
|
685
753
|
if not task_content:
|
|
686
|
-
|
|
754
|
+
console_err.print("[red]Error:[/red] Empty task")
|
|
687
755
|
sys.exit(1)
|
|
688
756
|
|
|
689
757
|
# Run the agent
|
|
690
758
|
runner = ExecutionRunner(config)
|
|
691
759
|
|
|
692
760
|
if not quiet:
|
|
693
|
-
|
|
761
|
+
console_err.print(f"[dim]Running {agent_name}...[/dim]")
|
|
694
762
|
|
|
695
763
|
result = runner.run(task_content, secrets=secrets_dict, output_format=output_format)
|
|
696
764
|
|
|
@@ -700,7 +768,7 @@ def run(
|
|
|
700
768
|
elif result["ok"]:
|
|
701
769
|
click.echo(result["data"])
|
|
702
770
|
else:
|
|
703
|
-
|
|
771
|
+
console_err.print(f"[red]Error:[/red] {result['error']}")
|
|
704
772
|
sys.exit(1)
|
|
705
773
|
|
|
706
774
|
|
|
@@ -756,7 +824,7 @@ def batch(
|
|
|
756
824
|
try:
|
|
757
825
|
config = load_agent_config(agent_name)
|
|
758
826
|
except AgentNotFoundError as e:
|
|
759
|
-
|
|
827
|
+
console_err.print(f"[red]Error:[/red] {e}")
|
|
760
828
|
sys.exit(1)
|
|
761
829
|
|
|
762
830
|
# Parse secrets
|
supyagent/core/llm.py
CHANGED
|
@@ -4,9 +4,13 @@ LiteLLM wrapper for unified LLM access.
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
import litellm
|
|
7
8
|
from litellm import completion
|
|
8
9
|
from litellm.types.utils import ModelResponse
|
|
9
10
|
|
|
11
|
+
# Suppress LiteLLM debug messages (e.g., "Provider List: ...")
|
|
12
|
+
litellm.suppress_debug_info = True
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class LLMClient:
|
|
12
16
|
"""
|
supyagent/core/tools.py
CHANGED
|
@@ -193,43 +193,61 @@ def supypowers_to_openai_tools(sp_tools: list[dict[str, Any]]) -> list[dict[str,
|
|
|
193
193
|
Convert supypowers tool definitions to OpenAI function calling format.
|
|
194
194
|
|
|
195
195
|
Supypowers docs output format:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
"script": "/path/to/script.py",
|
|
199
|
+
"functions": [
|
|
200
|
+
{
|
|
201
|
+
"name": "function_name",
|
|
202
|
+
"description": "...",
|
|
203
|
+
"input_schema": {...}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
]
|
|
202
208
|
|
|
203
209
|
OpenAI format:
|
|
204
210
|
{
|
|
205
211
|
"type": "function",
|
|
206
212
|
"function": {
|
|
207
|
-
"name": "
|
|
213
|
+
"name": "script__function_name",
|
|
208
214
|
"description": "...",
|
|
209
215
|
"parameters": {...}
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
"""
|
|
219
|
+
import os
|
|
220
|
+
|
|
213
221
|
openai_tools = []
|
|
214
222
|
|
|
215
|
-
for
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
223
|
+
for script_entry in sp_tools:
|
|
224
|
+
script_path = script_entry.get("script", "")
|
|
225
|
+
functions = script_entry.get("functions", [])
|
|
226
|
+
|
|
227
|
+
# Extract script name from path (e.g., "files" from "/path/to/files.py")
|
|
228
|
+
script_name = os.path.splitext(os.path.basename(script_path))[0]
|
|
229
|
+
|
|
230
|
+
# Skip __init__ files with no functions
|
|
231
|
+
if script_name == "__init__" and not functions:
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
for func_def in functions:
|
|
235
|
+
func_name = func_def.get("name", "")
|
|
236
|
+
description = func_def.get("description", "No description")
|
|
237
|
+
input_schema = func_def.get("input_schema", {"type": "object", "properties": {}})
|
|
238
|
+
|
|
239
|
+
# Use double underscore to join script:function (since : isn't allowed in function names)
|
|
240
|
+
name = f"{script_name}__{func_name}"
|
|
241
|
+
|
|
242
|
+
openai_tool = {
|
|
243
|
+
"type": "function",
|
|
244
|
+
"function": {
|
|
245
|
+
"name": name,
|
|
246
|
+
"description": description,
|
|
247
|
+
"parameters": input_schema,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
232
250
|
|
|
233
|
-
|
|
251
|
+
openai_tools.append(openai_tool)
|
|
234
252
|
|
|
235
253
|
return openai_tools
|
|
@@ -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))
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# dependencies = ["pydantic"]
|
|
3
|
+
# ///
|
|
4
|
+
"""
|
|
5
|
+
Shell command execution tools.
|
|
6
|
+
|
|
7
|
+
Allows agents to run bash/shell commands on the system.
|
|
8
|
+
Use with caution - consider restricting via agent tool permissions.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import shutil
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RunCommandInput(BaseModel):
|
|
20
|
+
"""Input for run_command function."""
|
|
21
|
+
|
|
22
|
+
command: str = Field(description="The shell command to execute")
|
|
23
|
+
working_dir: Optional[str] = Field(
|
|
24
|
+
default=None, description="Working directory for the command"
|
|
25
|
+
)
|
|
26
|
+
timeout: int = Field(
|
|
27
|
+
default=60, description="Maximum seconds to wait for command"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RunCommandOutput(BaseModel):
|
|
32
|
+
"""Output for run_command function."""
|
|
33
|
+
|
|
34
|
+
ok: bool
|
|
35
|
+
stdout: str
|
|
36
|
+
stderr: str
|
|
37
|
+
exit_code: int
|
|
38
|
+
command: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_command(input: RunCommandInput) -> RunCommandOutput:
|
|
42
|
+
"""
|
|
43
|
+
Execute a shell command and return the result.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
>>> run_command({"command": "ls -la"})
|
|
47
|
+
>>> run_command({"command": "echo 'hello' | grep hello"})
|
|
48
|
+
>>> run_command({"command": "pwd", "working_dir": "/tmp"})
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
working_dir = input.working_dir
|
|
52
|
+
if working_dir:
|
|
53
|
+
working_dir = os.path.expanduser(working_dir)
|
|
54
|
+
working_dir = os.path.expandvars(working_dir)
|
|
55
|
+
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
input.command,
|
|
58
|
+
shell=True,
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
timeout=input.timeout,
|
|
62
|
+
cwd=working_dir,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return RunCommandOutput(
|
|
66
|
+
ok=result.returncode == 0,
|
|
67
|
+
stdout=result.stdout,
|
|
68
|
+
stderr=result.stderr,
|
|
69
|
+
exit_code=result.returncode,
|
|
70
|
+
command=input.command,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
except subprocess.TimeoutExpired:
|
|
74
|
+
return RunCommandOutput(
|
|
75
|
+
ok=False,
|
|
76
|
+
stdout="",
|
|
77
|
+
stderr=f"Command timed out after {input.timeout} seconds",
|
|
78
|
+
exit_code=-1,
|
|
79
|
+
command=input.command,
|
|
80
|
+
)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
return RunCommandOutput(
|
|
83
|
+
ok=False,
|
|
84
|
+
stdout="",
|
|
85
|
+
stderr=str(e),
|
|
86
|
+
exit_code=-1,
|
|
87
|
+
command=input.command,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RunScriptInput(BaseModel):
|
|
92
|
+
"""Input for run_script function."""
|
|
93
|
+
|
|
94
|
+
script: str = Field(description="Multi-line script content")
|
|
95
|
+
interpreter: str = Field(
|
|
96
|
+
default="/bin/bash", description="Script interpreter"
|
|
97
|
+
)
|
|
98
|
+
working_dir: Optional[str] = Field(
|
|
99
|
+
default=None, description="Working directory"
|
|
100
|
+
)
|
|
101
|
+
timeout: int = Field(default=120, description="Max seconds to wait")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class RunScriptOutput(BaseModel):
|
|
105
|
+
"""Output for run_script function."""
|
|
106
|
+
|
|
107
|
+
ok: bool
|
|
108
|
+
stdout: str
|
|
109
|
+
stderr: str
|
|
110
|
+
exit_code: int
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def run_script(input: RunScriptInput) -> RunScriptOutput:
|
|
114
|
+
"""
|
|
115
|
+
Execute a multi-line shell script.
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
>>> run_script({"script": "cd /tmp\\necho $(pwd)\\nls"})
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
working_dir = input.working_dir
|
|
122
|
+
if working_dir:
|
|
123
|
+
working_dir = os.path.expanduser(working_dir)
|
|
124
|
+
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
[input.interpreter],
|
|
127
|
+
input=input.script,
|
|
128
|
+
capture_output=True,
|
|
129
|
+
text=True,
|
|
130
|
+
timeout=input.timeout,
|
|
131
|
+
cwd=working_dir,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return RunScriptOutput(
|
|
135
|
+
ok=result.returncode == 0,
|
|
136
|
+
stdout=result.stdout,
|
|
137
|
+
stderr=result.stderr,
|
|
138
|
+
exit_code=result.returncode,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
except subprocess.TimeoutExpired:
|
|
142
|
+
return RunScriptOutput(
|
|
143
|
+
ok=False,
|
|
144
|
+
stdout="",
|
|
145
|
+
stderr=f"Script timed out after {input.timeout} seconds",
|
|
146
|
+
exit_code=-1,
|
|
147
|
+
)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return RunScriptOutput(
|
|
150
|
+
ok=False,
|
|
151
|
+
stdout="",
|
|
152
|
+
stderr=str(e),
|
|
153
|
+
exit_code=-1,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class WhichInput(BaseModel):
|
|
158
|
+
"""Input for which function."""
|
|
159
|
+
|
|
160
|
+
command: str = Field(description="Command name to look up")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class WhichOutput(BaseModel):
|
|
164
|
+
"""Output for which function."""
|
|
165
|
+
|
|
166
|
+
ok: bool
|
|
167
|
+
path: Optional[str] = None
|
|
168
|
+
error: Optional[str] = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def which(input: WhichInput) -> WhichOutput:
|
|
172
|
+
"""
|
|
173
|
+
Find the path to an executable.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> which({"command": "python"})
|
|
177
|
+
>>> which({"command": "git"})
|
|
178
|
+
"""
|
|
179
|
+
path = shutil.which(input.command)
|
|
180
|
+
if path:
|
|
181
|
+
return WhichOutput(ok=True, path=path)
|
|
182
|
+
else:
|
|
183
|
+
return WhichOutput(ok=False, error=f"Command not found: {input.command}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class GetEnvInput(BaseModel):
|
|
187
|
+
"""Input for get_env function."""
|
|
188
|
+
|
|
189
|
+
name: str = Field(description="Environment variable name")
|
|
190
|
+
default: Optional[str] = Field(
|
|
191
|
+
default=None, description="Default if not set"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class GetEnvOutput(BaseModel):
|
|
196
|
+
"""Output for get_env function."""
|
|
197
|
+
|
|
198
|
+
ok: bool
|
|
199
|
+
value: Optional[str] = None
|
|
200
|
+
error: Optional[str] = None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def get_env(input: GetEnvInput) -> GetEnvOutput:
|
|
204
|
+
"""
|
|
205
|
+
Get an environment variable value.
|
|
206
|
+
|
|
207
|
+
Examples:
|
|
208
|
+
>>> get_env({"name": "HOME"})
|
|
209
|
+
>>> get_env({"name": "MY_VAR", "default": "fallback"})
|
|
210
|
+
"""
|
|
211
|
+
value = os.environ.get(input.name, input.default)
|
|
212
|
+
if value is not None:
|
|
213
|
+
return GetEnvOutput(ok=True, value=value)
|
|
214
|
+
else:
|
|
215
|
+
return GetEnvOutput(
|
|
216
|
+
ok=False, error=f"Environment variable not set: {input.name}"
|
|
217
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supyagent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: LLM agents powered by supypowers - build AI agents with tool use, multi-agent orchestration, and secure credential management
|
|
5
5
|
Project-URL: Homepage, https://github.com/ergodic-ai/supyagent
|
|
6
6
|
Project-URL: Documentation, https://github.com/ergodic-ai/supyagent#readme
|
|
@@ -68,6 +68,9 @@ uv pip install supyagent
|
|
|
68
68
|
## Quick Start
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
+
# Initialize supyagent (sets up default tools)
|
|
72
|
+
supyagent init
|
|
73
|
+
|
|
71
74
|
# Set up your API key (stored securely)
|
|
72
75
|
supyagent config set ANTHROPIC_API_KEY
|
|
73
76
|
|
|
@@ -229,6 +232,7 @@ delegates:
|
|
|
229
232
|
|
|
230
233
|
| Command | Description |
|
|
231
234
|
|---------|-------------|
|
|
235
|
+
| `supyagent init` | Initialize project with default tools |
|
|
232
236
|
| `supyagent new <name>` | Create a new agent |
|
|
233
237
|
| `supyagent list` | List all agents |
|
|
234
238
|
| `supyagent show <name>` | Show agent details |
|
|
@@ -281,6 +285,25 @@ While chatting, use these commands:
|
|
|
281
285
|
| `/clear` | Clear conversation history |
|
|
282
286
|
| `/quit` | Exit the chat |
|
|
283
287
|
|
|
288
|
+
## Bundled Tools
|
|
289
|
+
|
|
290
|
+
Running `supyagent init` installs these default tools:
|
|
291
|
+
|
|
292
|
+
### Shell (`shell.py`)
|
|
293
|
+
- `run_command` - Execute shell commands
|
|
294
|
+
- `run_script` - Run multi-line bash scripts
|
|
295
|
+
- `which` - Find executable paths
|
|
296
|
+
- `get_env` - Get environment variables
|
|
297
|
+
|
|
298
|
+
### Files (`files.py`)
|
|
299
|
+
- `read_file` / `write_file` - File I/O
|
|
300
|
+
- `list_directory` - List files with glob patterns
|
|
301
|
+
- `copy_file` / `move_file` / `delete_file` - File operations
|
|
302
|
+
- `create_directory` - Create directories
|
|
303
|
+
- `file_info` - Get file metadata
|
|
304
|
+
|
|
305
|
+
You can add your own tools by creating Python files in `supypowers/`.
|
|
306
|
+
|
|
284
307
|
## Project Structure
|
|
285
308
|
|
|
286
309
|
```
|
|
@@ -290,8 +313,9 @@ your-project/
|
|
|
290
313
|
│ ├── planner.yaml
|
|
291
314
|
│ └── researcher.yaml
|
|
292
315
|
├── supypowers/ # Tool definitions (Python)
|
|
293
|
-
│ ├──
|
|
294
|
-
│
|
|
316
|
+
│ ├── shell.py # Shell commands (bundled)
|
|
317
|
+
│ ├── files.py # File operations (bundled)
|
|
318
|
+
│ └── my_tools.py # Your custom tools
|
|
295
319
|
└── .supyagent/ # Runtime data (gitignore this)
|
|
296
320
|
├── sessions/ # Conversation history
|
|
297
321
|
├── credentials/ # Encrypted secrets
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
supyagent/__init__.py,sha256=JrrQbk1bmDhUb2qOqxqDGBq1V-oArwVS4f5f9Qu-VcM,77
|
|
2
2
|
supyagent/__main__.py,sha256=szUvsccEIA428c2_5J_X1qzq4L44qLvDvHfuRPYoy7c,121
|
|
3
3
|
supyagent/cli/__init__.py,sha256=7jb5kxHtzce4rNKd9zrAiATp3Zxj7UQvtpQsyA97BTc,32
|
|
4
|
-
supyagent/cli/main.py,sha256=
|
|
4
|
+
supyagent/cli/main.py,sha256=5YqOevjbDdGNhA_gIIqnSATWThxj87TB_59RHzyL5gQ,38434
|
|
5
5
|
supyagent/core/__init__.py,sha256=7MI6K2-OR_Fg0wcMziC4u3gNa0YxWmzJXO0RtOCevUg,727
|
|
6
6
|
supyagent/core/agent.py,sha256=mkiV6CnK6Vk0Wg-DiAMm29wAViMa52-1Yb33Y5-FvQY,12655
|
|
7
7
|
supyagent/core/config.py,sha256=kuTpuxy-qr-fhou7rJ-JLF82cdzdHQTu46kCZccSw1Y,10041
|
|
@@ -9,17 +9,20 @@ supyagent/core/context.py,sha256=ywen6IjWxcudxtipeqA_PHpZKdjK52eqOb9xy7QKnTM,456
|
|
|
9
9
|
supyagent/core/credentials.py,sha256=35JBrzMOEDzHnn3XuyGmRjXVdSICL3tups3BLO_NCXQ,7600
|
|
10
10
|
supyagent/core/delegation.py,sha256=B6OYlLjkZaRTPMQsK9nIGeXVO9JeC7RDpxXsOk652xs,9391
|
|
11
11
|
supyagent/core/executor.py,sha256=awJXseQKO0I-D-5YqGxJ9MEN1b2DTVk_8kOQZExFYhs,7893
|
|
12
|
-
supyagent/core/llm.py,sha256=
|
|
12
|
+
supyagent/core/llm.py,sha256=WpGGYARvVdnMGfZXNTf99bY7UqGJLPz0hdlIR4bb9Nk,2012
|
|
13
13
|
supyagent/core/registry.py,sha256=1CGCPsOcrIc_63_oJaFmHY8WwMgJEvbwsv8mnsOBWtY,7326
|
|
14
14
|
supyagent/core/session_manager.py,sha256=ngqrbofR8V0wZSuOfGHTtSYE0C9U6noDrzfDsO8wi24,7389
|
|
15
|
-
supyagent/core/tools.py,sha256=
|
|
15
|
+
supyagent/core/tools.py,sha256=qpCKdP5DBYj_XlCvMTskgiMQcUYSRWbsTolKmjkBO3w,7289
|
|
16
|
+
supyagent/default_tools/__init__.py,sha256=VwVjwNSPxjflmzJ-Mv_7L79Yjn9LdxyeXNUkWzu3wFo,1890
|
|
17
|
+
supyagent/default_tools/files.py,sha256=tUpjkP7TKbcK75lQ6jzEDpVDBSeupzTSqx_g2-rkON0,12059
|
|
18
|
+
supyagent/default_tools/shell.py,sha256=fYXdy693Nb0hsNLMzTFgj3zaHq0wFSQ3SxpqHbVayjM,5478
|
|
16
19
|
supyagent/models/__init__.py,sha256=y7fIdzh-yD0LKAK0zfY_MZdtjJhxofwRWBqrHDU93h0,259
|
|
17
20
|
supyagent/models/agent_config.py,sha256=sQ7S7zExoFs0g-IMgACsGH2SMiDRjJTnCVZwhN6DnkY,2570
|
|
18
21
|
supyagent/models/session.py,sha256=_Dwuija_Uvt_KzcNp82ja8qP5ltNHHz43Llug1vq1ic,1136
|
|
19
22
|
supyagent/utils/__init__.py,sha256=l4HdE3n8axphMInBex1J1dB_O1MjzbZz6ot1l8Syf0g,39
|
|
20
23
|
supyagent/utils/paths.py,sha256=Xum9kX_UjRvmshifSq423QsAKynpnyAVfpWuFjlsdDQ,692
|
|
21
|
-
supyagent-0.2.
|
|
22
|
-
supyagent-0.2.
|
|
23
|
-
supyagent-0.2.
|
|
24
|
-
supyagent-0.2.
|
|
25
|
-
supyagent-0.2.
|
|
24
|
+
supyagent-0.2.2.dist-info/METADATA,sha256=Fa3A8EH-0m7jeG1fMcHMunVogRK9iS0VFMf_0pM6Vf8,10387
|
|
25
|
+
supyagent-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
supyagent-0.2.2.dist-info/entry_points.txt,sha256=7dFXWtRewhLfA5XUckV7Yu1E5_c6c9lq8Gz-7BFs6TM,53
|
|
27
|
+
supyagent-0.2.2.dist-info/licenses/LICENSE,sha256=35fw1cvTM-IhiR3xaUohbiFBc2OBNRjI5z1b1cF7vZI,1067
|
|
28
|
+
supyagent-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|