agentr 0.1.3__py3-none-any.whl → 0.1.5__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.
@@ -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,58 +1,75 @@
1
- import typer
2
- from pathlib import Path
3
-
4
- app = typer.Typer()
5
-
6
- @app.command()
7
- def version():
8
- """Show the version of the CLI"""
9
- print("agentr version 0.1.0")
10
-
11
- @app.command()
12
- def generate(schema_path: Path = typer.Option(..., "--schema", "-s")):
13
- """Generate API client from OpenAPI schema"""
14
- if not schema_path.exists():
15
- typer.echo(f"Error: Schema file {schema_path} does not exist", err=True)
16
- raise typer.Exit(1)
17
- from .utils.openapi import generate_api_client, load_schema
18
-
19
- try:
20
- schema = load_schema(schema_path)
21
- except Exception as e:
22
- typer.echo(f"Error loading schema: {e}", err=True)
23
- raise typer.Exit(1)
24
- code = generate_api_client(schema)
25
- print(code)
26
-
27
- @app.command()
28
- def run():
29
- """Run the MCP server"""
30
- from agentr.test import mcp
31
- mcp.run()
32
-
33
- @app.command()
34
- def install(app_name: str):
35
- """Install an app"""
36
- import json
37
- api_key = typer.prompt("Enter your API key", hide_input=True)
38
- if app_name == "claude":
39
- typer.echo(f"Installing mcp server for: {app_name}")
40
- config_path = '~/Library/Application Support/Claude/claude_desktop_config.json'
41
- with open(config_path, 'r') as f:
42
- config = json.load(f)
43
- config['mcpServers']['agentr-r'] = {
44
- "command": "uvx",
45
- "args": ["agentr@latest", "run"],
46
- "env": {
47
- "AGENTR_API_KEY": api_key
48
- }
49
- }
50
- with open(config_path, 'w') as f:
51
- json.dump(config, f, indent=4)
52
- typer.echo("App installed successfully")
53
- else:
54
- typer.echo(f"App {app_name} not supported")
55
-
56
-
57
- if __name__ == "__main__":
58
- app()
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.test import mcp
32
+ mcp.run()
33
+
34
+ @app.command()
35
+ def install(app_name: str):
36
+ """Install an app"""
37
+ import json
38
+
39
+ # Print instructions before asking for API key
40
+ typer.echo("╭─ Instruction ─────────────────────────────────────────────────────────────────╮")
41
+ typer.echo("│ API key is required. Visit https://agentr.dev to create an API key. │")
42
+ typer.echo("╰───────────────────────────────────────────────────────────────────────────────╯")
43
+ # Prompt for API key
44
+ api_key = typer.prompt("Enter your AgentR API key", hide_input=True)
45
+
46
+ if app_name == "claude":
47
+ typer.echo(f"Installing mcp server for: {app_name}")
48
+
49
+ # Determine platform-specific config path
50
+ if sys.platform == "darwin": # macOS
51
+ config_path = Path.home() / "Library/Application Support/Claude/claude_desktop_config.json"
52
+ elif sys.platform == "win32": # Windows
53
+ config_path = Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
54
+ else:
55
+ typer.echo("Unsupported platform. Only macOS and Windows are currently supported.", err=True)
56
+ raise typer.Exit(1)
57
+
58
+
59
+ with open(config_path, 'r') as f:
60
+ config = json.load(f)
61
+ config['mcpServers']['agentr-r'] = {
62
+ "command": "uvx",
63
+ "args": ["agentr@latest", "run"],
64
+ "env": {
65
+ "AGENTR_API_KEY": api_key
66
+ }
67
+ }
68
+ with open(config_path, 'w') as f:
69
+ json.dump(config, f, indent=4)
70
+ typer.echo("App installed successfully")
71
+ else:
72
+ typer.echo(f"App {app_name} not supported")
73
+
74
+ if __name__ == "__main__":
75
+ app()
agentr/exceptions.py ADDED
@@ -0,0 +1,5 @@
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)
agentr/integration.py CHANGED
@@ -1,91 +1,98 @@
1
- from abc import ABC, abstractmethod
2
- import os
3
- from agentr.store import Store
4
- import httpx
5
-
6
- """
7
- Integration defines how a Application needs to authorize.
8
- It is responsible for authenticating application with the service provider.
9
- Supported integrations:
10
- - AgentR Integration
11
- - API Key Integration
12
- """
13
-
14
- class Integration(ABC):
15
- def __init__(self, name: str, store: Store = None):
16
- self.name = name
17
- self.store = store
18
-
19
- @abstractmethod
20
- def get_credentials(self):
21
- pass
22
-
23
- @abstractmethod
24
- def set_credentials(self, credentials: dict):
25
- pass
26
-
27
- class ApiKeyIntegration(Integration):
28
- def __init__(self, name: str, store: Store = None, **kwargs):
29
- super().__init__(name, store, **kwargs)
30
-
31
- def get_credentials(self):
32
- credentials = self.store.get(self.name)
33
- return credentials
34
-
35
- def set_credentials(self, credentials: dict):
36
- self.store.set(self.name, credentials)
37
-
38
- def authorize(self):
39
- return {"text": "Please configure the environment variable {self.name}_API_KEY"}
40
-
41
-
42
-
43
- class NangoIntegration(Integration):
44
- def __init__(self, user_id, integration_id):
45
- self.integration_id = integration_id
46
- self.user_id = user_id
47
- self.nango_secret_key = os.getenv("NANGO_SECRET_KEY")
48
-
49
- def _create_session_token(self):
50
- url = "https://api.nango.dev/connect/sessions"
51
- body = {
52
- "end_user": {
53
- "id": self.user_id,
54
- },
55
- "allowed_integrations": [self.integration_id]
56
- }
57
- response = httpx.post(url, headers={"Authorization": f"Bearer {self.nango_secret_key}"}, json=body)
58
- data = response.json()
59
- return data["data"]["token"]
60
-
61
- def get_authorize_url(self):
62
- session_token = self._create_session_token()
63
- return f"https://api.nango.dev/oauth/connect/{self.integration_id}?connect_session_token={session_token}"
64
-
65
- def get_connection_by_owner(self, user_id):
66
- url = f"https://api.nango.dev/connection?endUserId={user_id}"
67
- response = httpx.get(url, headers={"Authorization": f"Bearer {self.nango_secret_key}"})
68
- if response.status_code == 200:
69
- connections = response.json()["connections"]
70
- for connection in connections:
71
- if connection["provider_config_key"] == self.integration_id:
72
- return connection["connection_id"]
73
- return None
74
-
75
- class AgentRIntegration(Integration):
76
- def __init__(self, name: str, api_key: str = None, **kwargs):
77
- super().__init__(name, **kwargs)
78
- self.api_key = api_key or os.getenv("AGENTR_API_KEY")
79
- if not self.api_key:
80
- raise ValueError("api_key is required")
81
- self.base_url = "https://api.agentr.dev"
82
- self.user_id = "default"
83
-
84
- def get_credentials(self):
85
- response = httpx.get(f"{self.base_url}/integrations/{self.name}/credentials", headers={"Authorization": f"Bearer {self.api_key}"})
86
- return response.json()
87
-
88
- def authorize(self):
89
- response = httpx.post(f"{self.base_url}/integrations/{self.name}/authorize", headers={"Authorization": f"Bearer {self.api_key}"})
90
- url = response.json()["url"]
91
- return {"url": url, "text": "Please authorize the application by clicking the link {url}"}
1
+ from abc import ABC, abstractmethod
2
+ import os
3
+ import sys
4
+
5
+ from loguru import logger
6
+ from agentr.store import Store
7
+ import httpx
8
+
9
+ """
10
+ Integration defines how a Application needs to authorize.
11
+ It is responsible for authenticating application with the service provider.
12
+ Supported integrations:
13
+ - AgentR Integration
14
+ - API Key Integration
15
+ """
16
+
17
+ class Integration(ABC):
18
+ def __init__(self, name: str, store: Store = None):
19
+ self.name = name
20
+ self.store = store
21
+
22
+ @abstractmethod
23
+ def get_credentials(self):
24
+ pass
25
+
26
+ @abstractmethod
27
+ def set_credentials(self, credentials: dict):
28
+ pass
29
+
30
+ class ApiKeyIntegration(Integration):
31
+ def __init__(self, name: str, store: Store = None, **kwargs):
32
+ super().__init__(name, store, **kwargs)
33
+
34
+ def get_credentials(self):
35
+ credentials = self.store.get(self.name)
36
+ return credentials
37
+
38
+ def set_credentials(self, credentials: dict):
39
+ self.store.set(self.name, credentials)
40
+
41
+ def authorize(self):
42
+ return {"text": "Please configure the environment variable {self.name}_API_KEY"}
43
+
44
+
45
+
46
+ class AgentRIntegration(Integration):
47
+ def __init__(self, name: str, api_key: str = None, **kwargs):
48
+ super().__init__(name, **kwargs)
49
+ self.api_key = api_key or os.getenv("AGENTR_API_KEY")
50
+ if not self.api_key:
51
+ logger.error("API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable.")
52
+ raise ValueError("AgentR API key required - get one at https://agentr.dev")
53
+
54
+ self.base_url = "https://auth.agentr.dev"
55
+ self.user_id = "default"
56
+
57
+ def _create_session_token(self):
58
+ url = "https://auth.agentr.dev/connect/sessions"
59
+ body = {
60
+ "end_user": {
61
+ "id": self.user_id,
62
+ },
63
+ "allowed_integrations": [self.name]
64
+ }
65
+ response = httpx.post(url, headers={"Authorization": f"Bearer {self.api_key}"}, json=body)
66
+ data = response.json()
67
+ print(data)
68
+ return data["data"]["token"]
69
+
70
+ def _get_authorize_url(self):
71
+ session_token = self._create_session_token()
72
+ return f"https://auth.agentr.dev/oauth/connect/{self.name}?connect_session_token={session_token}"
73
+
74
+ def get_connection_by_owner(self):
75
+ url = f"https://auth.agentr.dev/connection?endUserId={self.user_id}"
76
+ response = httpx.get(url, headers={"Authorization": f"Bearer {self.api_key}"})
77
+ if response.status_code == 200:
78
+ connections = response.json()["connections"]
79
+ for connection in connections:
80
+ if connection["provider_config_key"] == self.name:
81
+ return connection["connection_id"]
82
+ return None
83
+
84
+ def set_credentials(self, credentials: dict):
85
+ raise NotImplementedError("AgentR Integration does not support setting credentials. Visit the authorize url to set credentials.")
86
+
87
+ def get_credentials(self):
88
+ connection_id = self.get_connection_by_owner()
89
+ logger.info(f"Connection ID: {connection_id}")
90
+ if connection_id:
91
+ response = httpx.get(f"{self.base_url}/connection/{connection_id}?provider_config_key={self.name}", headers={"Authorization": f"Bearer {self.api_key}"})
92
+ data = response.json()
93
+ return data.get("credentials")
94
+ return None
95
+
96
+ def authorize(self):
97
+ url = self._get_authorize_url()
98
+ return f"Please authorize the application by clicking the link {url}"