agentr 0.1.5__py3-none-any.whl → 0.1.7__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.
- agentr/__init__.py +1 -1
- agentr/application.py +92 -56
- agentr/applications/__init__.py +27 -0
- agentr/applications/github/app.py +408 -56
- agentr/applications/google_calendar/app.py +548 -74
- agentr/applications/google_mail/app.py +565 -68
- agentr/applications/reddit/app.py +29 -29
- agentr/applications/resend/app.py +43 -43
- agentr/applications/tavily/app.py +57 -57
- agentr/applications/zenquotes/app.py +20 -20
- agentr/cli.py +76 -75
- agentr/config.py +15 -0
- agentr/exceptions.py +6 -5
- agentr/integration.py +85 -98
- agentr/server.py +113 -105
- agentr/store.py +70 -70
- agentr/test.py +39 -37
- agentr/utils/openapi.py +185 -184
- {agentr-0.1.5.dist-info → agentr-0.1.7.dist-info}/METADATA +5 -2
- agentr-0.1.7.dist-info/RECORD +25 -0
- {agentr-0.1.5.dist-info → agentr-0.1.7.dist-info}/licenses/LICENSE +21 -21
- agentr-0.1.5.dist-info/RECORD +0 -23
- {agentr-0.1.5.dist-info → agentr-0.1.7.dist-info}/WHEEL +0 -0
- {agentr-0.1.5.dist-info → agentr-0.1.7.dist-info}/entry_points.txt +0 -0
@@ -1,29 +1,29 @@
|
|
1
|
-
from agentr.application import APIApplication
|
2
|
-
from agentr.integration import Integration
|
3
|
-
|
4
|
-
class RedditApp(APIApplication):
|
5
|
-
def __init__(self, integration: Integration) -> None:
|
6
|
-
super().__init__(name="reddit", integration=integration)
|
7
|
-
|
8
|
-
def _get_headers(self):
|
9
|
-
credentials = self.integration.get_credentials()
|
10
|
-
if "headers" in credentials:
|
11
|
-
return credentials["headers"]
|
12
|
-
return {
|
13
|
-
"Authorization": f"Bearer {credentials['access_token']}",
|
14
|
-
}
|
15
|
-
|
16
|
-
def get_subreddit_posts(self, subreddit: str) -> str:
|
17
|
-
"""Get the latest posts from a subreddit
|
18
|
-
|
19
|
-
Args:
|
20
|
-
subreddit: The subreddit to get posts from
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
A list of posts from the subreddit
|
24
|
-
"""
|
25
|
-
|
26
|
-
|
27
|
-
def list_tools(self):
|
28
|
-
return []
|
29
|
-
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class RedditApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
super().__init__(name="reddit", integration=integration)
|
7
|
+
|
8
|
+
def _get_headers(self):
|
9
|
+
credentials = self.integration.get_credentials()
|
10
|
+
if "headers" in credentials:
|
11
|
+
return credentials["headers"]
|
12
|
+
return {
|
13
|
+
"Authorization": f"Bearer {credentials['access_token']}",
|
14
|
+
}
|
15
|
+
|
16
|
+
def get_subreddit_posts(self, subreddit: str) -> str:
|
17
|
+
"""Get the latest posts from a subreddit
|
18
|
+
|
19
|
+
Args:
|
20
|
+
subreddit: The subreddit to get posts from
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
A list of posts from the subreddit
|
24
|
+
"""
|
25
|
+
|
26
|
+
|
27
|
+
def list_tools(self):
|
28
|
+
return []
|
29
|
+
|
@@ -1,43 +1,43 @@
|
|
1
|
-
from agentr.application import APIApplication
|
2
|
-
from agentr.integration import Integration
|
3
|
-
|
4
|
-
class ResendApp(APIApplication):
|
5
|
-
def __init__(self, integration: Integration) -> None:
|
6
|
-
super().__init__(name="resend", integration=integration)
|
7
|
-
|
8
|
-
def _get_headers(self):
|
9
|
-
credentials = self.integration.get_credentials()
|
10
|
-
if not credentials:
|
11
|
-
raise ValueError("No credentials found")
|
12
|
-
return {
|
13
|
-
"Authorization": f"Bearer {credentials['api_key']}",
|
14
|
-
}
|
15
|
-
|
16
|
-
def send_email(self, to: str, subject: str, content: str) -> str:
|
17
|
-
"""Send an email using the Resend API
|
18
|
-
|
19
|
-
Args:
|
20
|
-
to: The email address to send the email to
|
21
|
-
subject: The subject of the email
|
22
|
-
content: The content of the email
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
A message indicating that the email was sent successfully
|
26
|
-
"""
|
27
|
-
credentials = self.integration.get_credentials()
|
28
|
-
if not credentials:
|
29
|
-
raise ValueError("No credentials found")
|
30
|
-
from_email = credentials.get("from_email", "Manoj <manoj@agentr.dev>")
|
31
|
-
url = "https://api.resend.com/emails"
|
32
|
-
body = {
|
33
|
-
"from": from_email,
|
34
|
-
"to": [to],
|
35
|
-
"subject": subject,
|
36
|
-
"text": content
|
37
|
-
}
|
38
|
-
self._post(url, body)
|
39
|
-
return "Sent Successfully"
|
40
|
-
|
41
|
-
def list_tools(self):
|
42
|
-
return [self.send_email]
|
43
|
-
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class ResendApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
super().__init__(name="resend", integration=integration)
|
7
|
+
|
8
|
+
def _get_headers(self):
|
9
|
+
credentials = self.integration.get_credentials()
|
10
|
+
if not credentials:
|
11
|
+
raise ValueError("No credentials found")
|
12
|
+
return {
|
13
|
+
"Authorization": f"Bearer {credentials['api_key']}",
|
14
|
+
}
|
15
|
+
|
16
|
+
def send_email(self, to: str, subject: str, content: str) -> str:
|
17
|
+
"""Send an email using the Resend API
|
18
|
+
|
19
|
+
Args:
|
20
|
+
to: The email address to send the email to
|
21
|
+
subject: The subject of the email
|
22
|
+
content: The content of the email
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A message indicating that the email was sent successfully
|
26
|
+
"""
|
27
|
+
credentials = self.integration.get_credentials()
|
28
|
+
if not credentials:
|
29
|
+
raise ValueError("No credentials found")
|
30
|
+
from_email = credentials.get("from_email", "Manoj <manoj@agentr.dev>")
|
31
|
+
url = "https://api.resend.com/emails"
|
32
|
+
body = {
|
33
|
+
"from": from_email,
|
34
|
+
"to": [to],
|
35
|
+
"subject": subject,
|
36
|
+
"text": content
|
37
|
+
}
|
38
|
+
self._post(url, body)
|
39
|
+
return "Sent Successfully"
|
40
|
+
|
41
|
+
def list_tools(self):
|
42
|
+
return [self.send_email]
|
43
|
+
|
@@ -1,57 +1,57 @@
|
|
1
|
-
from agentr.application import APIApplication
|
2
|
-
from agentr.integration import Integration
|
3
|
-
|
4
|
-
class TavilyApp(APIApplication):
|
5
|
-
def __init__(self, integration: Integration) -> None:
|
6
|
-
name = "tavily"
|
7
|
-
self.base_url = "https://api.tavily.com"
|
8
|
-
super().__init__(name=name, integration=integration)
|
9
|
-
|
10
|
-
def _get_headers(self):
|
11
|
-
credentials = self.integration.get_credentials()
|
12
|
-
if not credentials:
|
13
|
-
raise ValueError("No credentials found")
|
14
|
-
return {
|
15
|
-
"Authorization": f"Bearer {credentials['api_key']}",
|
16
|
-
"Content-Type": "application/json"
|
17
|
-
}
|
18
|
-
|
19
|
-
def search(self, query: str) -> str:
|
20
|
-
"""Search the web using Tavily's search API
|
21
|
-
|
22
|
-
Args:
|
23
|
-
query: The search query
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
str: A summary of search results
|
27
|
-
"""
|
28
|
-
self.validate()
|
29
|
-
url = f"{self.base_url}/search"
|
30
|
-
payload = {
|
31
|
-
"query": query,
|
32
|
-
"topic": "general",
|
33
|
-
"search_depth": "basic",
|
34
|
-
"max_results": 3,
|
35
|
-
"include_answer": True,
|
36
|
-
"include_raw_content": False,
|
37
|
-
"include_images": False,
|
38
|
-
"include_image_descriptions": False,
|
39
|
-
"include_domains": [],
|
40
|
-
"exclude_domains": []
|
41
|
-
}
|
42
|
-
|
43
|
-
response = self._post(url, payload)
|
44
|
-
result = response.json()
|
45
|
-
|
46
|
-
if "answer" in result:
|
47
|
-
return result["answer"]
|
48
|
-
|
49
|
-
# Fallback to combining top results if no direct answer
|
50
|
-
summaries = []
|
51
|
-
for item in result.get("results", [])[:3]:
|
52
|
-
summaries.append(f"• {item['title']}: {item['snippet']}")
|
53
|
-
|
54
|
-
return "\n".join(summaries)
|
55
|
-
|
56
|
-
def list_tools(self):
|
57
|
-
return [self.search]
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
from agentr.integration import Integration
|
3
|
+
|
4
|
+
class TavilyApp(APIApplication):
|
5
|
+
def __init__(self, integration: Integration) -> None:
|
6
|
+
name = "tavily"
|
7
|
+
self.base_url = "https://api.tavily.com"
|
8
|
+
super().__init__(name=name, integration=integration)
|
9
|
+
|
10
|
+
def _get_headers(self):
|
11
|
+
credentials = self.integration.get_credentials()
|
12
|
+
if not credentials:
|
13
|
+
raise ValueError("No credentials found")
|
14
|
+
return {
|
15
|
+
"Authorization": f"Bearer {credentials['api_key']}",
|
16
|
+
"Content-Type": "application/json"
|
17
|
+
}
|
18
|
+
|
19
|
+
def search(self, query: str) -> str:
|
20
|
+
"""Search the web using Tavily's search API
|
21
|
+
|
22
|
+
Args:
|
23
|
+
query: The search query
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
str: A summary of search results
|
27
|
+
"""
|
28
|
+
self.validate()
|
29
|
+
url = f"{self.base_url}/search"
|
30
|
+
payload = {
|
31
|
+
"query": query,
|
32
|
+
"topic": "general",
|
33
|
+
"search_depth": "basic",
|
34
|
+
"max_results": 3,
|
35
|
+
"include_answer": True,
|
36
|
+
"include_raw_content": False,
|
37
|
+
"include_images": False,
|
38
|
+
"include_image_descriptions": False,
|
39
|
+
"include_domains": [],
|
40
|
+
"exclude_domains": []
|
41
|
+
}
|
42
|
+
|
43
|
+
response = self._post(url, payload)
|
44
|
+
result = response.json()
|
45
|
+
|
46
|
+
if "answer" in result:
|
47
|
+
return result["answer"]
|
48
|
+
|
49
|
+
# Fallback to combining top results if no direct answer
|
50
|
+
summaries = []
|
51
|
+
for item in result.get("results", [])[:3]:
|
52
|
+
summaries.append(f"• {item['title']}: {item['snippet']}")
|
53
|
+
|
54
|
+
return "\n".join(summaries)
|
55
|
+
|
56
|
+
def list_tools(self):
|
57
|
+
return [self.search]
|
@@ -1,21 +1,21 @@
|
|
1
|
-
from agentr.application import APIApplication
|
2
|
-
|
3
|
-
|
4
|
-
class ZenQuoteApp(APIApplication):
|
5
|
-
def __init__(self, **kwargs) -> None:
|
6
|
-
super().__init__(name="zenquote", **kwargs)
|
7
|
-
|
8
|
-
def get_quote(self) -> str:
|
9
|
-
"""Get an inspirational quote from the Zen Quotes API
|
10
|
-
|
11
|
-
Returns:
|
12
|
-
A random inspirational quote
|
13
|
-
"""
|
14
|
-
url = "https://zenquotes.io/api/random"
|
15
|
-
response = self._get(url)
|
16
|
-
data = response.json()
|
17
|
-
quote_data = data[0]
|
18
|
-
return f"{quote_data['q']} - {quote_data['a']}"
|
19
|
-
|
20
|
-
def list_tools(self):
|
1
|
+
from agentr.application import APIApplication
|
2
|
+
|
3
|
+
|
4
|
+
class ZenQuoteApp(APIApplication):
|
5
|
+
def __init__(self, **kwargs) -> None:
|
6
|
+
super().__init__(name="zenquote", **kwargs)
|
7
|
+
|
8
|
+
def get_quote(self) -> str:
|
9
|
+
"""Get an inspirational quote from the Zen Quotes API
|
10
|
+
|
11
|
+
Returns:
|
12
|
+
A random inspirational quote
|
13
|
+
"""
|
14
|
+
url = "https://zenquotes.io/api/random"
|
15
|
+
response = self._get(url)
|
16
|
+
data = response.json()
|
17
|
+
quote_data = data[0]
|
18
|
+
return f"{quote_data['q']} - {quote_data['a']}"
|
19
|
+
|
20
|
+
def list_tools(self):
|
21
21
|
return [self.get_quote]
|
agentr/cli.py
CHANGED
@@ -1,75 +1,76 @@
|
|
1
|
-
import typer
|
2
|
-
from pathlib import Path
|
3
|
-
import sys
|
4
|
-
|
5
|
-
app = typer.Typer()
|
6
|
-
|
7
|
-
@app.command()
|
8
|
-
def version():
|
9
|
-
"""Show the version of the CLI"""
|
10
|
-
print("agentr version 0.1.0")
|
11
|
-
|
12
|
-
@app.command()
|
13
|
-
def generate(schema_path: Path = typer.Option(..., "--schema", "-s")):
|
14
|
-
"""Generate API client from OpenAPI schema"""
|
15
|
-
if not schema_path.exists():
|
16
|
-
typer.echo(f"Error: Schema file {schema_path} does not exist", err=True)
|
17
|
-
raise typer.Exit(1)
|
18
|
-
from .utils.openapi import generate_api_client, load_schema
|
19
|
-
|
20
|
-
try:
|
21
|
-
schema = load_schema(schema_path)
|
22
|
-
except Exception as e:
|
23
|
-
typer.echo(f"Error loading schema: {e}", err=True)
|
24
|
-
raise typer.Exit(1)
|
25
|
-
code = generate_api_client(schema)
|
26
|
-
print(code)
|
27
|
-
|
28
|
-
@app.command()
|
29
|
-
def run():
|
30
|
-
"""Run the MCP server"""
|
31
|
-
from agentr.
|
32
|
-
mcp
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
typer.echo("
|
42
|
-
typer.echo("
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
"
|
64
|
-
"
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
1
|
+
import typer
|
2
|
+
from pathlib import Path
|
3
|
+
import sys
|
4
|
+
|
5
|
+
app = typer.Typer()
|
6
|
+
|
7
|
+
@app.command()
|
8
|
+
def version():
|
9
|
+
"""Show the version of the CLI"""
|
10
|
+
print("agentr version 0.1.0")
|
11
|
+
|
12
|
+
@app.command()
|
13
|
+
def generate(schema_path: Path = typer.Option(..., "--schema", "-s")):
|
14
|
+
"""Generate API client from OpenAPI schema"""
|
15
|
+
if not schema_path.exists():
|
16
|
+
typer.echo(f"Error: Schema file {schema_path} does not exist", err=True)
|
17
|
+
raise typer.Exit(1)
|
18
|
+
from .utils.openapi import generate_api_client, load_schema
|
19
|
+
|
20
|
+
try:
|
21
|
+
schema = load_schema(schema_path)
|
22
|
+
except Exception as e:
|
23
|
+
typer.echo(f"Error loading schema: {e}", err=True)
|
24
|
+
raise typer.Exit(1)
|
25
|
+
code = generate_api_client(schema)
|
26
|
+
print(code)
|
27
|
+
|
28
|
+
@app.command()
|
29
|
+
def run():
|
30
|
+
"""Run the MCP server"""
|
31
|
+
from agentr.server import AgentRServer
|
32
|
+
mcp = AgentRServer(name="AgentR Server", description="AgentR Server")
|
33
|
+
mcp.run()
|
34
|
+
|
35
|
+
@app.command()
|
36
|
+
def install(app_name: str):
|
37
|
+
"""Install an app"""
|
38
|
+
import json
|
39
|
+
|
40
|
+
# Print instructions before asking for API key
|
41
|
+
typer.echo("╭─ Instruction ─────────────────────────────────────────────────────────────────╮")
|
42
|
+
typer.echo("│ API key is required. Visit https://agentr.dev to create an API key. │")
|
43
|
+
typer.echo("╰───────────────────────────────────────────────────────────────────────────────╯")
|
44
|
+
# Prompt for API key
|
45
|
+
api_key = typer.prompt("Enter your AgentR API key", hide_input=True)
|
46
|
+
|
47
|
+
if app_name == "claude":
|
48
|
+
typer.echo(f"Installing mcp server for: {app_name}")
|
49
|
+
|
50
|
+
# Determine platform-specific config path
|
51
|
+
if sys.platform == "darwin": # macOS
|
52
|
+
config_path = Path.home() / "Library/Application Support/Claude/claude_desktop_config.json"
|
53
|
+
elif sys.platform == "win32": # Windows
|
54
|
+
config_path = Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
|
55
|
+
else:
|
56
|
+
typer.echo("Unsupported platform. Only macOS and Windows are currently supported.", err=True)
|
57
|
+
raise typer.Exit(1)
|
58
|
+
|
59
|
+
|
60
|
+
with open(config_path, 'r') as f:
|
61
|
+
config = json.load(f)
|
62
|
+
config['mcpServers']['agentr'] = {
|
63
|
+
"command": "uvx",
|
64
|
+
"args": ["agentr@latest", "run"],
|
65
|
+
"env": {
|
66
|
+
"AGENTR_API_KEY": api_key
|
67
|
+
}
|
68
|
+
}
|
69
|
+
with open(config_path, 'w') as f:
|
70
|
+
json.dump(config, f, indent=4)
|
71
|
+
typer.echo("App installed successfully")
|
72
|
+
else:
|
73
|
+
typer.echo(f"App {app_name} not supported")
|
74
|
+
|
75
|
+
if __name__ == "__main__":
|
76
|
+
app()
|
agentr/config.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
class StoreConfig(BaseModel):
|
5
|
+
type: Literal["memory", "environment"]
|
6
|
+
|
7
|
+
class IntegrationConfig(BaseModel):
|
8
|
+
name: str
|
9
|
+
type: Literal["api_key", "agentr"]
|
10
|
+
credentials: dict | None = None
|
11
|
+
store: StoreConfig | None = None
|
12
|
+
|
13
|
+
class AppConfig(BaseModel):
|
14
|
+
name: str
|
15
|
+
integration: IntegrationConfig | None = None
|
agentr/exceptions.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
class NotAuthorizedError(Exception):
|
2
|
-
"""Raised when a user is not authorized to access a resource or perform an action."""
|
3
|
-
def __init__(self, message="Not authorized to perform this action"):
|
4
|
-
self.message = message
|
5
|
-
super().__init__(self.message)
|
1
|
+
class NotAuthorizedError(Exception):
|
2
|
+
"""Raised when a user is not authorized to access a resource or perform an action."""
|
3
|
+
def __init__(self, message="Not authorized to perform this action"):
|
4
|
+
self.message = message
|
5
|
+
super().__init__(self.message)
|
6
|
+
|