cred-crewai 0.1.0__tar.gz

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,50 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+
5
+ # Sprint artifacts
6
+ .factory/
7
+ .sprint-*
8
+ .sprint-pid
9
+ .rewire-done
10
+ .content-rewrite-done
11
+ .hubspot-done
12
+ .integrations-done
13
+ .done
14
+ .validation-report.md
15
+ .sprint-logs/
16
+ .sprint-prompts/
17
+ SESSION_PACKAGE.md
18
+ run-sprint.sh
19
+ validate.sh
20
+
21
+ # Internal planning docs (not for public repo)
22
+ CLAUDE.md
23
+ PLAN.md
24
+ HANDOFF.md
25
+ VALIDATION.md
26
+ MARKETING-VALIDATION.md
27
+ FINAL-SUBMISSION-VALIDATION.md
28
+ .next/
29
+ out/
30
+ coverage/
31
+ *.tsbuildinfo
32
+ .turbo/
33
+ .DS_Store
34
+ *.pem
35
+ .env
36
+ .env.local
37
+ .env*.local
38
+ !.env.example
39
+
40
+ # Python
41
+ __pycache__/
42
+ *.py[cod]
43
+ *.pyo
44
+ *.egg-info/
45
+ .pytest_cache/
46
+ .venv/
47
+ venv/
48
+ dist/
49
+ *.dist-info/
50
+ packages/sdk-python/.venv/
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: cred-crewai
3
+ Version: 0.1.0
4
+ Summary: Cred CrewAI integration — pre-configured credential delegation tools for CrewAI agents
5
+ Author-email: Cred <hello@cred.ninja>
6
+ License: Apache-2.0
7
+ Keywords: ai-agents,cred,crewai,delegation,oauth
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: cred-auth>=0.1.0
10
+ Requires-Dist: crewai>=0.11
11
+ Requires-Dist: langchain-core>=0.2
12
+ Provides-Extra: dev
13
+ Requires-Dist: hatchling; extra == 'dev'
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # cred-crewai
18
+
19
+ CrewAI integration for Cred. OAuth2 credential delegation for AI agents.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install cred-crewai
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ import os
31
+ from cred_crewai import CredTool
32
+ from crewai import Agent
33
+
34
+ # Create a pre-configured tool for Google Calendar
35
+ google_tool = CredTool(
36
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
37
+ user_id="user_123",
38
+ service="google",
39
+ app_client_id="my_app_client_id",
40
+ scopes=["calendar.readonly"],
41
+ )
42
+
43
+ # Use with CrewAI agent
44
+ agent = Agent(
45
+ role="Calendar Manager",
46
+ goal="Manage user's calendar",
47
+ tools=[google_tool],
48
+ )
49
+ ```
50
+
51
+ ## Key Differences from LangChain
52
+
53
+ **LangChain integration** (`cred-langchain`):
54
+ - `CredToolkit` returns 3 generic tools: delegate, status, revoke
55
+ - Best for agents that need full credential management
56
+
57
+ **CrewAI integration** (`cred-crewai`):
58
+ - `CredTool` is pre-configured for a **single service**
59
+ - Tool name auto-generated: `cred_google_delegate`, `cred_github_delegate`, etc.
60
+ - Best for specialized agents (e.g., a "Calendar Manager" agent only needs Google tokens)
61
+
62
+ One `CredTool` per service per agent = clearer intent, smaller decision space for the LLM.
63
+
64
+ ## Handling Consent
65
+
66
+ When a user hasn't connected the service yet, `_run()` raises `ConsentRequiredError`.
67
+ Catch it and redirect the user to `e.consent_url` to complete the OAuth flow.
68
+
69
+ ## Cred Cloud (Coming Soon)
70
+
71
+ Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
@@ -0,0 +1,55 @@
1
+ # cred-crewai
2
+
3
+ CrewAI integration for Cred. OAuth2 credential delegation for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install cred-crewai
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import os
15
+ from cred_crewai import CredTool
16
+ from crewai import Agent
17
+
18
+ # Create a pre-configured tool for Google Calendar
19
+ google_tool = CredTool(
20
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
21
+ user_id="user_123",
22
+ service="google",
23
+ app_client_id="my_app_client_id",
24
+ scopes=["calendar.readonly"],
25
+ )
26
+
27
+ # Use with CrewAI agent
28
+ agent = Agent(
29
+ role="Calendar Manager",
30
+ goal="Manage user's calendar",
31
+ tools=[google_tool],
32
+ )
33
+ ```
34
+
35
+ ## Key Differences from LangChain
36
+
37
+ **LangChain integration** (`cred-langchain`):
38
+ - `CredToolkit` returns 3 generic tools: delegate, status, revoke
39
+ - Best for agents that need full credential management
40
+
41
+ **CrewAI integration** (`cred-crewai`):
42
+ - `CredTool` is pre-configured for a **single service**
43
+ - Tool name auto-generated: `cred_google_delegate`, `cred_github_delegate`, etc.
44
+ - Best for specialized agents (e.g., a "Calendar Manager" agent only needs Google tokens)
45
+
46
+ One `CredTool` per service per agent = clearer intent, smaller decision space for the LLM.
47
+
48
+ ## Handling Consent
49
+
50
+ When a user hasn't connected the service yet, `_run()` raises `ConsentRequiredError`.
51
+ Catch it and redirect the user to `e.consent_url` to complete the OAuth flow.
52
+
53
+ ## Cred Cloud (Coming Soon)
54
+
55
+ Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
@@ -0,0 +1,5 @@
1
+ """Cred CrewAI integration — pre-configured delegation tools for CrewAI agents."""
2
+
3
+ from .tool import CredTool
4
+
5
+ __all__ = ["CredTool"]
@@ -0,0 +1,105 @@
1
+ """Cred CrewAI Tool — pre-configured delegation tool for a single service.
2
+
3
+ CrewAI's BaseTool is langchain_core.tools.BaseTool under the hood
4
+ (verified in crewai 0.11.x source). We import from langchain_core so this
5
+ package works in environments where crewai is installed alongside langchain,
6
+ without requiring crewai as a hard build dependency at import time.
7
+
8
+ When crewai is installed, pass CredTool instances directly to Agent(tools=[...]).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ from typing import Any, Optional, Type
15
+
16
+ from langchain_core.tools import BaseTool
17
+ from pydantic import BaseModel, Field
18
+
19
+ from cred import Cred
20
+ from cred.exceptions import ConsentRequiredError # re-export for convenience
21
+
22
+
23
+ class _CredToolInput(BaseModel):
24
+ """Placeholder input — CredTool has no runtime inputs; all params are pre-configured."""
25
+ pass
26
+
27
+
28
+ class CredTool(BaseTool):
29
+ """A CrewAI-compatible tool that delegates credentials for a single service.
30
+
31
+ Pre-configured at construction time with a specific service, user, and
32
+ optional scopes. The agent calls it with no additional parameters — the
33
+ service/user context is baked in, keeping the agent's decision surface small.
34
+
35
+ Example::
36
+
37
+ from cred_crewai import CredTool
38
+
39
+ google_tool = CredTool(
40
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
41
+ user_id="user_123",
42
+ service="google",
43
+ app_client_id="my_app",
44
+ scopes=["calendar.readonly"],
45
+ )
46
+
47
+ # Use with CrewAI
48
+ from crewai import Agent
49
+ agent = Agent(role="Calendar Manager", tools=[google_tool])
50
+ """
51
+
52
+ # langchain_core requires these as class-level annotations
53
+ name: str = "cred_delegate"
54
+ description: str = "Get a delegated OAuth access token for a service"
55
+ args_schema: Type[BaseModel] = _CredToolInput
56
+
57
+ # Tool state — set in __init__
58
+ _cred: Any = None
59
+ _user_id: str = ""
60
+ _service: str = ""
61
+ _app_client_id: str = ""
62
+ _scopes: list[str] = []
63
+ _cached_token: str | None = None
64
+ _cache_expires_at: float = 0.0
65
+
66
+ def __init__(
67
+ self,
68
+ agent_token: str,
69
+ user_id: str,
70
+ service: str,
71
+ app_client_id: str,
72
+ scopes: Optional[list[str]] = None,
73
+ base_url: str = "https://api.cred.ninja",
74
+ **kwargs: Any,
75
+ ) -> None:
76
+ super().__init__(**kwargs)
77
+ self._cred = Cred(agent_token=agent_token, base_url=base_url)
78
+ self._user_id = user_id
79
+ self._service = service
80
+ self._app_client_id = app_client_id
81
+ self._scopes = scopes or []
82
+ # Set dynamic name and description after init
83
+ self.name = f"cred_{service.replace('-', '_')}_delegate"
84
+ self.description = (
85
+ f"Get a delegated OAuth access token for {service} on behalf of the current user. "
86
+ f"Returns the access token string. "
87
+ f"Raises ConsentRequiredError (with consent_url) if the user hasn't connected {service}."
88
+ )
89
+
90
+ def _run(self, **kwargs: Any) -> str:
91
+ """Delegate credentials and return the access token string."""
92
+ now = time.monotonic()
93
+ if self._cached_token is not None and self._cache_expires_at > now:
94
+ return self._cached_token
95
+
96
+ result = self._cred.delegate(
97
+ service=self._service,
98
+ user_id=self._user_id,
99
+ app_client_id=self._app_client_id,
100
+ scopes=self._scopes if self._scopes else None,
101
+ )
102
+ self._cached_token = result.access_token
103
+ expires_in = getattr(result, "expires_in", None)
104
+ self._cache_expires_at = now + expires_in - 60 if isinstance(expires_in, int) and expires_in > 60 else 0.0
105
+ return result.access_token
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "cred-crewai"
7
+ version = "0.1.0"
8
+ description = "Cred CrewAI integration — pre-configured credential delegation tools for CrewAI agents"
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Cred", email = "hello@cred.ninja" }]
13
+ keywords = ["cred", "crewai", "oauth", "delegation", "ai-agents"]
14
+ dependencies = [
15
+ "cred-auth>=0.1.0",
16
+ "crewai>=0.11",
17
+ "langchain-core>=0.2",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = ["pytest>=7.0", "hatchling"]
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["cred_crewai"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,247 @@
1
+ """Cred CrewAI Tool tests.
2
+
3
+ Mocks the Cred client — no real HTTP, no real API calls.
4
+ Tests focus on: tool configuration, naming, input schema, output format, error propagation.
5
+ """
6
+
7
+ import warnings
8
+ warnings.filterwarnings("ignore", category=UserWarning)
9
+
10
+ from datetime import datetime, timezone
11
+ from typing import Optional
12
+
13
+ import pytest
14
+ import httpx
15
+ from unittest.mock import MagicMock, patch
16
+
17
+ from cred import DelegationResult, ConsentRequiredError
18
+ from cred_crewai import CredTool
19
+
20
+ TOKEN = "cred_at_test"
21
+ USER_ID = "user_123"
22
+ APP_CLIENT_ID = "app_1"
23
+
24
+
25
+ def make_delegation_result(
26
+ *,
27
+ access_token: str = "at",
28
+ token_type: str = "Bearer",
29
+ expires_in: int = 3600,
30
+ service: str = "google",
31
+ scopes: Optional[list[str]] = None,
32
+ delegation_id: str = "del_1",
33
+ ) -> DelegationResult:
34
+ return DelegationResult(
35
+ access_token=access_token,
36
+ token_type=token_type,
37
+ expires_in=expires_in,
38
+ expires_at=datetime(2026, 3, 1, tzinfo=timezone.utc),
39
+ service=service,
40
+ scopes=scopes or [],
41
+ delegation_id=delegation_id,
42
+ )
43
+
44
+
45
+ @pytest.fixture
46
+ def mock_cred():
47
+ return MagicMock()
48
+
49
+
50
+ @pytest.fixture
51
+ def tool(mock_cred):
52
+ t = CredTool(
53
+ agent_token=TOKEN,
54
+ user_id=USER_ID,
55
+ service="google",
56
+ app_client_id=APP_CLIENT_ID,
57
+ scopes=["calendar.readonly"],
58
+ )
59
+ t._cred = mock_cred
60
+ return t
61
+
62
+
63
+ # ── constructor & configuration ───────────────────────────────────────────────
64
+
65
+ class TestConstructor:
66
+ def test_sets_service_from_param(self):
67
+ t = CredTool(
68
+ agent_token=TOKEN,
69
+ user_id=USER_ID,
70
+ service="github",
71
+ app_client_id=APP_CLIENT_ID,
72
+ )
73
+ assert t._service == "github"
74
+ assert "github" in t.name
75
+
76
+ def test_dynamic_name_includes_service(self):
77
+ google_tool = CredTool(
78
+ agent_token=TOKEN,
79
+ user_id=USER_ID,
80
+ service="google-calendar",
81
+ app_client_id=APP_CLIENT_ID,
82
+ )
83
+ assert google_tool.name == "cred_google_calendar_delegate"
84
+
85
+ def test_dynamic_description_includes_service(self):
86
+ t = CredTool(
87
+ agent_token=TOKEN,
88
+ user_id=USER_ID,
89
+ service="github",
90
+ app_client_id=APP_CLIENT_ID,
91
+ )
92
+ assert "github" in t.description
93
+ assert "access token" in t.description
94
+
95
+ def test_stores_user_id(self):
96
+ t = CredTool(
97
+ agent_token=TOKEN,
98
+ user_id="user_456",
99
+ service="google",
100
+ app_client_id=APP_CLIENT_ID,
101
+ )
102
+ assert t._user_id == "user_456"
103
+
104
+ def test_stores_scopes(self):
105
+ scopes = ["calendar.readonly", "calendar.events"]
106
+ t = CredTool(
107
+ agent_token=TOKEN,
108
+ user_id=USER_ID,
109
+ service="google",
110
+ app_client_id=APP_CLIENT_ID,
111
+ scopes=scopes,
112
+ )
113
+ assert t._scopes == scopes
114
+
115
+ def test_defaults_scopes_to_empty_list(self):
116
+ t = CredTool(
117
+ agent_token=TOKEN,
118
+ user_id=USER_ID,
119
+ service="google",
120
+ app_client_id=APP_CLIENT_ID,
121
+ )
122
+ assert t._scopes == []
123
+
124
+ def test_custom_base_url(self):
125
+ with patch("cred_crewai.tool.Cred") as mock_cred_class:
126
+ CredTool(
127
+ agent_token=TOKEN,
128
+ user_id=USER_ID,
129
+ service="google",
130
+ app_client_id=APP_CLIENT_ID,
131
+ base_url="http://localhost:3001",
132
+ )
133
+ mock_cred_class.assert_called_once_with(
134
+ agent_token=TOKEN,
135
+ base_url="http://localhost:3001",
136
+ )
137
+
138
+
139
+ # ── _run() ────────────────────────────────────────────────────────────────────
140
+
141
+ class TestRun:
142
+ def test_returns_access_token_string(self, tool, mock_cred):
143
+ mock_cred.delegate.return_value = make_delegation_result(
144
+ access_token="ya29.mock",
145
+ expires_in=3600,
146
+ service="google",
147
+ scopes=["calendar.readonly"],
148
+ delegation_id="del_abc",
149
+ )
150
+
151
+ result = tool._run()
152
+
153
+ assert result == "ya29.mock"
154
+ assert isinstance(result, str)
155
+
156
+ def test_passes_service_user_app_client_id(self, tool, mock_cred):
157
+ mock_cred.delegate.return_value = make_delegation_result()
158
+
159
+ tool._run()
160
+
161
+ mock_cred.delegate.assert_called_once_with(
162
+ service="google",
163
+ user_id=USER_ID,
164
+ app_client_id=APP_CLIENT_ID,
165
+ scopes=["calendar.readonly"],
166
+ )
167
+
168
+ def test_passes_scopes_when_configured(self, tool, mock_cred):
169
+ tool._scopes = ["calendar.readonly", "calendar.events"]
170
+ mock_cred.delegate.return_value = make_delegation_result(
171
+ scopes=["calendar.readonly", "calendar.events"],
172
+ )
173
+
174
+ tool._run()
175
+
176
+ call_args = mock_cred.delegate.call_args
177
+ assert call_args.kwargs["scopes"] == ["calendar.readonly", "calendar.events"]
178
+
179
+ def test_passes_none_scopes_when_empty(self, mock_cred):
180
+ t = CredTool(
181
+ agent_token=TOKEN,
182
+ user_id=USER_ID,
183
+ service="google",
184
+ app_client_id=APP_CLIENT_ID,
185
+ )
186
+ t._cred = mock_cred
187
+ t._scopes = []
188
+
189
+ mock_cred.delegate.return_value = make_delegation_result()
190
+
191
+ t._run()
192
+
193
+ call_args = mock_cred.delegate.call_args
194
+ assert call_args.kwargs["scopes"] is None
195
+
196
+ def test_propagates_consent_required_error(self, tool, mock_cred):
197
+ mock_cred.delegate.side_effect = ConsentRequiredError(
198
+ "User has not consented",
199
+ "https://api.cred.ninja/api/connect/google/authorize?app_client_id=app_1",
200
+ )
201
+
202
+ with pytest.raises(ConsentRequiredError) as exc_info:
203
+ tool._run()
204
+
205
+ assert "/api/connect/google/authorize" in exc_info.value.consent_url
206
+
207
+
208
+ # ── langchain compatibility ───────────────────────────────────────────────────
209
+
210
+ class TestLangChainCompatibility:
211
+ def test_has_args_schema(self, tool):
212
+ assert hasattr(tool, "args_schema")
213
+ schema = tool.args_schema.model_json_schema()
214
+ # Placeholder schema should be minimal
215
+ assert isinstance(schema, dict)
216
+
217
+ def test_is_base_tool(self, tool):
218
+ from langchain_core.tools import BaseTool
219
+ assert isinstance(tool, BaseTool)
220
+
221
+
222
+ class TestIntegrationFlow:
223
+ def test_real_cred_client_delegates_against_mock_server(self):
224
+ tool = CredTool(
225
+ agent_token=TOKEN,
226
+ user_id=USER_ID,
227
+ service="google",
228
+ app_client_id=APP_CLIENT_ID,
229
+ scopes=["calendar.readonly"],
230
+ base_url="https://cred.example.com",
231
+ )
232
+
233
+ with patch("httpx.Client.request", return_value=httpx.Response(
234
+ 200,
235
+ json={
236
+ "access_token": "ya29.real",
237
+ "token_type": "Bearer",
238
+ "expires_in": 3600,
239
+ "service": "google",
240
+ "scopes": ["calendar.readonly"],
241
+ "delegation_id": "del_real",
242
+ },
243
+ )) as request_mock:
244
+ token = tool._run()
245
+
246
+ assert token == "ya29.real"
247
+ assert request_mock.called