Skip to content

Commit 3df0935

Browse files
authored
Merge pull request #65 from tcdent/issue-57
Remove tools.json
2 parents 446cf6c + 28c1a4f commit 3df0935

25 files changed

Lines changed: 238 additions & 143 deletions

agentstack/cli/cli.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import time
44
from datetime import datetime
55
from typing import Optional
6+
import itertools
67

78
from art import text2art
89
import inquirer
@@ -12,6 +13,8 @@
1213

1314
from .agentstack_data import FrameworkData, ProjectMetadata, ProjectStructure, CookiecutterData
1415
from agentstack.logger import log
16+
from agentstack.utils import get_package_path
17+
from agentstack.generation.tool_generation import get_all_tools
1518
from .. import generation
1619
from ..utils import open_json_file, term_color, is_snake_case
1720

@@ -276,20 +279,20 @@ def insert_template(project_details: dict, framework_name: str, design: dict):
276279
structure=project_structure,
277280
framework=framework_name.lower())
278281

279-
with importlib.resources.path(f'agentstack.templates', str(framework.name)) as template_path:
280-
with open(f"{template_path}/cookiecutter.json", "w") as json_file:
281-
json.dump(cookiecutter_data.to_dict(), json_file)
282+
template_path = get_package_path() / f'templates/{framework.name}'
283+
with open(f"{template_path}/cookiecutter.json", "w") as json_file:
284+
json.dump(cookiecutter_data.to_dict(), json_file)
282285

283-
# copy .env.example to .env
284-
shutil.copy(
285-
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env.example',
286-
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env')
286+
# copy .env.example to .env
287+
shutil.copy(
288+
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env.example',
289+
f'{template_path}/{"{{cookiecutter.project_metadata.project_slug}}"}/.env')
287290

288-
if os.path.isdir(project_details['name']):
289-
print(term_color(f"Directory {template_path} already exists. Please check this and try again", "red"))
290-
return
291+
if os.path.isdir(project_details['name']):
292+
print(term_color(f"Directory {template_path} already exists. Please check this and try again", "red"))
293+
return
291294

292-
cookiecutter(str(template_path), no_input=True, extra_context=None)
295+
cookiecutter(str(template_path), no_input=True, extra_context=None)
293296

294297
# TODO: inits a git repo in the directory the command was run in
295298
# TODO: not where the project is generated. Fix this
@@ -324,24 +327,19 @@ def add_tools(tools: list, project_name: str):
324327

325328

326329
def list_tools():
327-
with importlib.resources.path(f'agentstack.tools', 'tools.json') as tools_json_path:
328-
try:
329-
# Load the JSON data
330-
tools_data = open_json_file(tools_json_path)
331-
332-
# Display the tools
333-
print("\n\nAvailable AgentStack Tools:")
334-
for category, tools in tools_data.items():
335-
print(f"\n{category.capitalize()}:")
336-
for tool in tools:
337-
print(f" - {tool['name']}: {tool['url']}")
338-
339-
print("\n\n✨ Add a tool with: agentstack tools add <tool_name>")
340-
print(" https://docs.agentstack.sh/tools/core")
341-
342-
except FileNotFoundError:
343-
print("Error: tools.json file not found at path:", tools_json_path)
344-
except json.JSONDecodeError:
345-
print("Error: tools.json contains invalid JSON.")
346-
except Exception as e:
347-
print(f"An unexpected error occurred: {e}")
330+
# Display the tools
331+
tools = get_all_tools()
332+
curr_category = None
333+
334+
print("\n\nAvailable AgentStack Tools:")
335+
for category, tools in itertools.groupby(tools, lambda x: x.category):
336+
if curr_category != category:
337+
print(f"\n{category}:")
338+
curr_category = category
339+
for tool in tools:
340+
print(" - ", end='')
341+
print(term_color(f"{tool.name}", 'blue'), end='')
342+
print(f": {tool.url if tool.url else 'AgentStack default tool'}")
343+
344+
print("\n\n✨ Add a tool with: agentstack tools add <tool_name>")
345+
print(" https://docs.agentstack.sh/tools/core")

agentstack/generation/tool_generation.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,53 @@
1+
import os, sys
2+
from typing import Optional, Any, List
13
import importlib.resources
24
from pathlib import Path
35
import json
4-
import sys
5-
from typing import Optional, List
6+
import shutil
7+
import fileinput
68
from pydantic import BaseModel, ValidationError
79

10+
from agentstack.utils import get_package_path
811
from .gen_utils import insert_code_after_tag, string_in_file
912
from ..utils import open_json_file, get_framework, term_color
10-
import os
11-
import shutil
12-
import fileinput
13+
1314

1415
TOOL_INIT_FILENAME = "src/tools/__init__.py"
15-
TOOLS_DATA_PATH: Path = importlib.resources.files('agentstack.tools') / 'tools.json'
1616
AGENTSTACK_JSON_FILENAME = "agentstack.json"
1717
FRAMEWORK_FILENAMES: dict[str, str] = {
1818
'crewai': 'src/crew.py',
1919
}
2020

2121
def get_framework_filename(framework: str, path: str = ''):
22+
if path:
23+
path = path.endswith('/') and path or path + '/'
24+
else:
25+
path = './'
2226
try:
23-
return FRAMEWORK_FILENAMES[framework]
27+
return f"{path}{FRAMEWORK_FILENAMES[framework]}"
2428
except KeyError:
2529
print(term_color(f'Unknown framework: {framework}', 'red'))
2630
sys.exit(1)
2731

28-
def assert_tool_exists(name: str):
29-
tools_data = open_json_file(TOOLS_DATA_PATH)
30-
for category, tools in tools_data.items():
31-
for tool_dict in tools:
32-
if tool_dict['name'] == name:
33-
return
34-
print(term_color(f'No known agentstack tool: {name}', 'red'))
35-
sys.exit(1)
36-
3732
class ToolConfig(BaseModel):
3833
name: str
34+
category: str
3935
tools: list[str]
36+
url: Optional[str] = None
4037
tools_bundled: bool = False
4138
cta: Optional[str] = None
42-
env: Optional[str] = None
39+
env: Optional[dict] = None
4340
packages: Optional[List[str]] = None
4441
post_install: Optional[str] = None
4542
post_remove: Optional[str] = None
4643

4744
@classmethod
4845
def from_tool_name(cls, name: str) -> 'ToolConfig':
49-
assert_tool_exists(name)
50-
return cls.from_json(importlib.resources.files('agentstack.tools') / f'{name}.json')
46+
path = get_package_path() / f'tools/{name}.json'
47+
if not os.path.exists(path):
48+
print(term_color(f'No known agentstack tool: {name}', 'red'))
49+
sys.exit(1)
50+
return cls.from_json(path)
5151

5252
@classmethod
5353
def from_json(cls, path: Path) -> 'ToolConfig':
@@ -62,6 +62,23 @@ def from_json(cls, path: Path) -> 'ToolConfig':
6262

6363
def get_import_statement(self) -> str:
6464
return f"from .{self.name}_tool import {', '.join(self.tools)}"
65+
66+
def get_impl_file_path(self, framework: str) -> Path:
67+
return get_package_path() / f'templates/{framework}/tools/{self.name}_tool.py'
68+
69+
def get_all_tool_paths() -> list[Path]:
70+
paths = []
71+
tools_dir = get_package_path() / 'tools'
72+
for file in tools_dir.iterdir():
73+
if file.is_file() and file.suffix == '.json':
74+
paths.append(file)
75+
return paths
76+
77+
def get_all_tool_names() -> list[str]:
78+
return [path.stem for path in get_all_tool_paths()]
79+
80+
def get_all_tools() -> list[ToolConfig]:
81+
return [ToolConfig.from_json(path) for path in get_all_tool_paths()]
6582

6683
def add_tool(tool_name: str, path: Optional[str] = None):
6784
if path:
@@ -77,18 +94,23 @@ def add_tool(tool_name: str, path: Optional[str] = None):
7794
sys.exit(1)
7895

7996
tool_data = ToolConfig.from_tool_name(tool_name)
80-
tool_file_path = importlib.resources.files(f'agentstack.templates.{framework}.tools') / f'{tool_name}_tool.py'
97+
tool_file_path = tool_data.get_impl_file_path(framework)
8198
if tool_data.packages:
8299
os.system(f"poetry add {' '.join(tool_data.packages)}") # Install packages
83100
shutil.copy(tool_file_path, f'{path}src/tools/{tool_name}_tool.py') # Move tool from package to project
84101
add_tool_to_tools_init(tool_data, path) # Export tool from tools dir
85102
add_tool_to_agent_definition(framework, tool_data, path) # Add tool to agent definition
86103
if tool_data.env: # if the env vars aren't in the .env files, add them
87-
first_var_name = tool_data.env.split('=')[0]
88-
if not string_in_file(f'{path}.env', first_var_name):
89-
insert_code_after_tag(f'{path}.env', '# Tools', [tool_data.env], next_line=True) # Add env var
90-
if not string_in_file(f'{path}.env.example', first_var_name):
91-
insert_code_after_tag(f'{path}.env.example', '# Tools', [tool_data.env], next_line=True) # Add env var
104+
# tool_data.env is a dict, key is the env var name, value is the value
105+
for var, value in tool_data.env.items():
106+
env_var = f'{var}={value}'
107+
if not string_in_file(f'{path}.env', env_var):
108+
insert_code_after_tag(f'{path}.env', '# Tools', [env_var, ])
109+
if not string_in_file(f'{path}.env.example', env_var):
110+
insert_code_after_tag(f'{path}.env.example', '# Tools', [env_var, ])
111+
112+
if tool_data.post_install:
113+
os.system(tool_data.post_install)
92114

93115
if tool_data.post_install:
94116
os.system(tool_data.post_install)
Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
{
2-
"name": "agent-connect",
3-
"packages": ["agent-connect"],
4-
"env": "HOST_DOMAIN=...\nHOST_PORT=\"80\"\nHOST_WS_PATH=\"/ws\"\nDID_DOCUMENT_PATH=...\nSSL_CERT_PATH=...\nSSL_KEY_PATH=...",
5-
"tools": ["send_message", "receive_message"]
2+
"name": "agent_connect",
3+
"url": "https://github.com/chgaowei/AgentConnect",
4+
"category": "network-protocols",
5+
"packages": ["agent-connect"],
6+
"env": {
7+
"HOST_DOMAIN": "...",
8+
"HOST_PORT": 80,
9+
"HOST_WS_PATH": "/ws",
10+
"DID_DOCUMENT_PATH": "...",
11+
"SSL_CERT_PATH": "...",
12+
"SSL_KEY_PATH": "..."
13+
},
14+
"tools": ["send_message", "receive_message"]
615
}

agentstack/tools/browserbase.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
{
22
"name": "browserbase",
3+
"url": "https://github.com/browserbase/python-sdk",
4+
"category": "browsing",
35
"packages": ["browserbase", "playwright"],
4-
"env": "BROWSERBASE_API_KEY=...\nBROWSERBASE_PROJECT_ID=...",
6+
"env": {
7+
"BROWSERBASE_API_KEY": "...",
8+
"BROWSERBASE_PROJECT_ID": "..."
9+
},
510
"tools": ["browserbase"],
611
"cta": "Create an API key at https://www.browserbase.com/"
712
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"name": "code_interpreter",
3-
"packages": [],
4-
"env": "",
3+
"category": "code-execution",
54
"tools": ["code_interpreter"]
65
}

agentstack/tools/composio.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"name": "composio",
3+
"url": "https://composio.dev/",
4+
"category": "unified-apis",
35
"packages": ["composio-crewai"],
4-
"env": "COMPOSIO_API_KEY=...",
6+
"env": {
7+
"COMPOSIO_API_KEY": "..."
8+
},
59
"tools": ["composio_tools"],
610
"tools_bundled": true,
711
"cta": "!!! Composio provides 150+ tools. Additional setup is required in src/tools/composio_tool.py"
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "dir_search_tool",
3-
"packages": [],
4-
"env": "",
2+
"name": "directory_search",
3+
"url": "https://github.com/crewAIInc/crewAI-tools/tree/main/crewai_tools/tools/directory_search_tool",
4+
"category": "computer-control",
55
"tools": ["dir_search_tool"]
66
}

agentstack/tools/exa.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"name": "exa",
3+
"url": "https://exa.ai",
4+
"category": "web-retrieval",
35
"packages": ["exa_py"],
4-
"env": "EXA_API_KEY=...",
6+
"env": {
7+
"EXA_API_KEY": "..."
8+
},
59
"tools": ["search_and_contents"],
610
"cta": "Get your Exa API key at https://dashboard.exa.ai/api-keys"
711
}

agentstack/tools/file_read.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "file_read_tool",
3-
"packages": [],
4-
"env": "",
2+
"name": "file_read",
3+
"url": "https://github.com/crewAIInc/crewAI-tools/tree/main/crewai_tools/tools/file_read_tool",
4+
"category": "computer-control",
55
"tools": ["file_read_tool"]
66
}

agentstack/tools/firecrawl.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"name": "firecrawl",
3+
"url": "https://www.firecrawl.dev/",
4+
"category": "browsing",
35
"packages": ["firecrawl-py"],
4-
"env": "FIRECRAWL_API_KEY=...",
6+
"env": {
7+
"FIRECRAWL_API_KEY": "..."
8+
},
59
"tools": ["web_scrape", "web_crawl", "retrieve_web_crawl"],
610
"cta": "Create an API key at https://www.firecrawl.dev/"
711
}

0 commit comments

Comments
 (0)