provision-stack-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.
- provision_stack_crewai-0.1.0/.gitignore +34 -0
- provision_stack_crewai-0.1.0/PKG-INFO +85 -0
- provision_stack_crewai-0.1.0/README.md +58 -0
- provision_stack_crewai-0.1.0/provision_stack_crewai/__init__.py +18 -0
- provision_stack_crewai-0.1.0/provision_stack_crewai/tools.py +252 -0
- provision_stack_crewai-0.1.0/pyproject.toml +51 -0
- provision_stack_crewai-0.1.0/tests/test_tools.py +115 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
node_modules
|
|
2
|
+
dist
|
|
3
|
+
build
|
|
4
|
+
coverage
|
|
5
|
+
|
|
6
|
+
.env
|
|
7
|
+
.env.*
|
|
8
|
+
!.env.example
|
|
9
|
+
|
|
10
|
+
.turbo
|
|
11
|
+
|
|
12
|
+
.DS_Store
|
|
13
|
+
|
|
14
|
+
.vscode/settings.json
|
|
15
|
+
|
|
16
|
+
*.log
|
|
17
|
+
|
|
18
|
+
# Generated Terraform deployment artifacts (written to user's cwd, not repo)
|
|
19
|
+
terraform/
|
|
20
|
+
|
|
21
|
+
# Local SQLite state (DATABASE_PATH default location)
|
|
22
|
+
data/
|
|
23
|
+
|
|
24
|
+
# Local deploy secrets (NEVER commit — deploy-time env lives here)
|
|
25
|
+
.env.local
|
|
26
|
+
deploy/.env
|
|
27
|
+
|
|
28
|
+
# Host SSH keys + terraform state (local artifacts)
|
|
29
|
+
deploy/hosting/provision-stack-host-key
|
|
30
|
+
deploy/hosting/provision-stack-host-key.pub
|
|
31
|
+
deploy/hosting/.terraform/
|
|
32
|
+
deploy/hosting/terraform.tfstate*
|
|
33
|
+
deploy/hosting/.terraform.lock.hcl
|
|
34
|
+
__pycache__/
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: provision-stack-crewai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CrewAI tools for Provision Stack — deploy cloud infrastructure via AI agents.
|
|
5
|
+
Project-URL: Homepage, https://provision-stack.com
|
|
6
|
+
Project-URL: Documentation, https://docs.provision-stack.com
|
|
7
|
+
Project-URL: Repository, https://github.com/TravisLinkey/provision-stack
|
|
8
|
+
Author: Provision Stack
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: ai-agents,aws,crewai,gcp,infrastructure,mcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: crewai>=0.100.0
|
|
22
|
+
Requires-Dist: provision-stack>=0.1.0
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest-mock>=3.12; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# provision-stack-crewai
|
|
29
|
+
|
|
30
|
+
> CrewAI tools for Provision Stack — deploy cloud infrastructure via AI agents.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install provision-stack-crewai
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from provision_stack_crewai import get_provision_stack_tools
|
|
42
|
+
from crewai import Agent, Task, Crew
|
|
43
|
+
|
|
44
|
+
tools = get_provision_stack_tools(api_key="your-token", sandbox=True)
|
|
45
|
+
|
|
46
|
+
engineer = Agent(
|
|
47
|
+
role="Infrastructure Engineer",
|
|
48
|
+
goal="Deploy production infrastructure",
|
|
49
|
+
tools=tools,
|
|
50
|
+
llm="gpt-4o",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
task = Task(
|
|
54
|
+
description="Deploy a production web application with a managed database",
|
|
55
|
+
agent=engineer,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
crew = Crew(agents=[engineer], tasks=[task])
|
|
59
|
+
result = crew.kickoff()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
| Tool | Description |
|
|
65
|
+
|------|-------------|
|
|
66
|
+
| `provision_stack_suggest` | Get deployment suggestions with tier options and costs |
|
|
67
|
+
| `provision_stack_deploy` | Deploy infrastructure (fee held, captured after verification) |
|
|
68
|
+
| `provision_stack_status` | Check deployment status and verification results |
|
|
69
|
+
| `provision_stack_destroy` | Destroy all resources owned by a deployment |
|
|
70
|
+
| `provision_stack_balance` | Check account credit balance |
|
|
71
|
+
| `provision_stack_topup` | Add credits via Stripe or XRP |
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
tools = get_provision_stack_tools(
|
|
77
|
+
api_key="your-token", # or PROVISION_STACK_API_KEY env var
|
|
78
|
+
base_url="https://...", # or PROVISION_STACK_API_URL env var
|
|
79
|
+
sandbox=True, # use sandbox (mock Terraform)
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# provision-stack-crewai
|
|
2
|
+
|
|
3
|
+
> CrewAI tools for Provision Stack — deploy cloud infrastructure via AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install provision-stack-crewai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from provision_stack_crewai import get_provision_stack_tools
|
|
15
|
+
from crewai import Agent, Task, Crew
|
|
16
|
+
|
|
17
|
+
tools = get_provision_stack_tools(api_key="your-token", sandbox=True)
|
|
18
|
+
|
|
19
|
+
engineer = Agent(
|
|
20
|
+
role="Infrastructure Engineer",
|
|
21
|
+
goal="Deploy production infrastructure",
|
|
22
|
+
tools=tools,
|
|
23
|
+
llm="gpt-4o",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
task = Task(
|
|
27
|
+
description="Deploy a production web application with a managed database",
|
|
28
|
+
agent=engineer,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
crew = Crew(agents=[engineer], tasks=[task])
|
|
32
|
+
result = crew.kickoff()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Tools
|
|
36
|
+
|
|
37
|
+
| Tool | Description |
|
|
38
|
+
|------|-------------|
|
|
39
|
+
| `provision_stack_suggest` | Get deployment suggestions with tier options and costs |
|
|
40
|
+
| `provision_stack_deploy` | Deploy infrastructure (fee held, captured after verification) |
|
|
41
|
+
| `provision_stack_status` | Check deployment status and verification results |
|
|
42
|
+
| `provision_stack_destroy` | Destroy all resources owned by a deployment |
|
|
43
|
+
| `provision_stack_balance` | Check account credit balance |
|
|
44
|
+
| `provision_stack_topup` | Add credits via Stripe or XRP |
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
tools = get_provision_stack_tools(
|
|
50
|
+
api_key="your-token", # or PROVISION_STACK_API_KEY env var
|
|
51
|
+
base_url="https://...", # or PROVISION_STACK_API_URL env var
|
|
52
|
+
sandbox=True, # use sandbox (mock Terraform)
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provision Stack tools for CrewAI.
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
from provision_stack_crewai import get_provision_stack_tools
|
|
7
|
+
|
|
8
|
+
tools = get_provision_stack_tools(api_key="your-token")
|
|
9
|
+
agent = Agent(
|
|
10
|
+
role="Infrastructure Engineer",
|
|
11
|
+
tools=tools,
|
|
12
|
+
...
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from provision_stack_crewai.tools import get_provision_stack_tools
|
|
17
|
+
|
|
18
|
+
__all__ = ["get_provision_stack_tools"]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CrewAI tool wrappers for Provision Stack.
|
|
3
|
+
|
|
4
|
+
Each tool is a CrewAI ``BaseTool`` subclass that wraps the
|
|
5
|
+
corresponding Provision Stack API endpoint.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Type
|
|
11
|
+
|
|
12
|
+
from crewai.tools import BaseTool
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
from provision_stack.client import ProvisionStackClient
|
|
16
|
+
from provision_stack.config import PRODUCT_NAME
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── Pydantic schemas for tool inputs ──────────────────────────────
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SuggestInput(BaseModel):
|
|
23
|
+
"""Input for getting deployment suggestions."""
|
|
24
|
+
|
|
25
|
+
outcome: str = Field(
|
|
26
|
+
...,
|
|
27
|
+
description="Natural language description of desired infrastructure "
|
|
28
|
+
"(e.g. 'deploy a production web application').",
|
|
29
|
+
)
|
|
30
|
+
region: str | None = Field(
|
|
31
|
+
None,
|
|
32
|
+
description="Cloud region (e.g. 'us-east-1', 'europe-west1').",
|
|
33
|
+
)
|
|
34
|
+
provider: str | None = Field(
|
|
35
|
+
None,
|
|
36
|
+
description="Restrict to one provider: 'aws', 'gcp', 'azure', 'oracle'.",
|
|
37
|
+
)
|
|
38
|
+
max_monthly_cost_usd: float | None = Field(
|
|
39
|
+
None,
|
|
40
|
+
description="Monthly cost cap in USD.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DeployInput(BaseModel):
|
|
45
|
+
"""Input for deploying infrastructure."""
|
|
46
|
+
|
|
47
|
+
outcome: str = Field(
|
|
48
|
+
...,
|
|
49
|
+
description="Natural language description of what to deploy.",
|
|
50
|
+
)
|
|
51
|
+
tier: str | None = Field(
|
|
52
|
+
None,
|
|
53
|
+
description="Deployment tier: 'Starter', 'MVP', 'Startup', 'Enterprise'.",
|
|
54
|
+
)
|
|
55
|
+
region: str | None = Field(
|
|
56
|
+
None,
|
|
57
|
+
description="Target cloud region.",
|
|
58
|
+
)
|
|
59
|
+
max_monthly_cost_usd: float | None = Field(
|
|
60
|
+
None,
|
|
61
|
+
description="Monthly cost cap in USD.",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class StatusInput(BaseModel):
|
|
66
|
+
"""Input for checking deployment status."""
|
|
67
|
+
|
|
68
|
+
deployment_id: str = Field(
|
|
69
|
+
...,
|
|
70
|
+
description="The deployment ID to check.",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DestroyInput(BaseModel):
|
|
75
|
+
"""Input for destroying a deployment."""
|
|
76
|
+
|
|
77
|
+
deployment_id: str = Field(
|
|
78
|
+
...,
|
|
79
|
+
description="The deployment ID to destroy.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TopupInput(BaseModel):
|
|
84
|
+
"""Input for adding credits."""
|
|
85
|
+
|
|
86
|
+
amount_usd: float = Field(
|
|
87
|
+
...,
|
|
88
|
+
description="Amount to add in USD.",
|
|
89
|
+
)
|
|
90
|
+
rail: str = Field(
|
|
91
|
+
"stripe",
|
|
92
|
+
description="Payment rail: 'stripe' (card) or 'xrp' (crypto).",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── CrewAI tools ──────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ProvisionStackSuggestTool(BaseTool):
|
|
100
|
+
"""Get deployment suggestions for a desired outcome."""
|
|
101
|
+
|
|
102
|
+
name: str = "provision_stack_suggest"
|
|
103
|
+
description: str = (
|
|
104
|
+
f"Get deployment suggestions from {PRODUCT_NAME}. "
|
|
105
|
+
"Returns tier options with monthly cost estimates."
|
|
106
|
+
)
|
|
107
|
+
args_schema: Type[BaseModel] = SuggestInput
|
|
108
|
+
client: ProvisionStackClient
|
|
109
|
+
|
|
110
|
+
def _run(
|
|
111
|
+
self,
|
|
112
|
+
outcome: str,
|
|
113
|
+
region: str | None = None,
|
|
114
|
+
provider: str | None = None,
|
|
115
|
+
max_monthly_cost_usd: float | None = None,
|
|
116
|
+
) -> str:
|
|
117
|
+
import json
|
|
118
|
+
|
|
119
|
+
result = self.client.suggest(
|
|
120
|
+
outcome=outcome,
|
|
121
|
+
region=region,
|
|
122
|
+
provider=provider,
|
|
123
|
+
max_monthly_cost_usd=max_monthly_cost_usd,
|
|
124
|
+
)
|
|
125
|
+
return json.dumps(result, indent=2)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ProvisionStackDeployTool(BaseTool):
|
|
129
|
+
"""Deploy infrastructure for a selected suggestion."""
|
|
130
|
+
|
|
131
|
+
name: str = "provision_stack_deploy"
|
|
132
|
+
description: str = (
|
|
133
|
+
f"Deploy infrastructure via {PRODUCT_NAME}. "
|
|
134
|
+
"Fee held from balance, captured after verification (failed = free)."
|
|
135
|
+
)
|
|
136
|
+
args_schema: Type[BaseModel] = DeployInput
|
|
137
|
+
client: ProvisionStackClient
|
|
138
|
+
|
|
139
|
+
def _run(
|
|
140
|
+
self,
|
|
141
|
+
outcome: str,
|
|
142
|
+
tier: str | None = None,
|
|
143
|
+
region: str | None = None,
|
|
144
|
+
max_monthly_cost_usd: float | None = None,
|
|
145
|
+
) -> str:
|
|
146
|
+
import json
|
|
147
|
+
|
|
148
|
+
result = self.client.deploy(
|
|
149
|
+
outcome=outcome,
|
|
150
|
+
tier=tier,
|
|
151
|
+
region=region,
|
|
152
|
+
max_monthly_cost_usd=max_monthly_cost_usd,
|
|
153
|
+
)
|
|
154
|
+
return json.dumps(result, indent=2)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ProvisionStackStatusTool(BaseTool):
|
|
158
|
+
"""Check deployment status and verification results."""
|
|
159
|
+
|
|
160
|
+
name: str = "provision_stack_status"
|
|
161
|
+
description: str = f"Check status and verification of a {PRODUCT_NAME} deployment."
|
|
162
|
+
args_schema: Type[BaseModel] = StatusInput
|
|
163
|
+
client: ProvisionStackClient
|
|
164
|
+
|
|
165
|
+
def _run(self, deployment_id: str) -> str:
|
|
166
|
+
import json
|
|
167
|
+
|
|
168
|
+
result = self.client.status(deployment_id)
|
|
169
|
+
return json.dumps(result, indent=2)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ProvisionStackDestroyTool(BaseTool):
|
|
173
|
+
"""Destroy all resources owned by a deployment."""
|
|
174
|
+
|
|
175
|
+
name: str = "provision_stack_destroy"
|
|
176
|
+
description: str = f"Destroy all cloud resources owned by a {PRODUCT_NAME} deployment."
|
|
177
|
+
args_schema: Type[BaseModel] = DestroyInput
|
|
178
|
+
client: ProvisionStackClient
|
|
179
|
+
|
|
180
|
+
def _run(self, deployment_id: str) -> str:
|
|
181
|
+
import json
|
|
182
|
+
|
|
183
|
+
result = self.client.destroy(deployment_id)
|
|
184
|
+
return json.dumps(result, indent=2)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ProvisionStackBalanceTool(BaseTool):
|
|
188
|
+
"""Check account credit balance."""
|
|
189
|
+
|
|
190
|
+
name: str = "provision_stack_balance"
|
|
191
|
+
description: str = f"Check your {PRODUCT_NAME} account credit balance."
|
|
192
|
+
client: ProvisionStackClient
|
|
193
|
+
|
|
194
|
+
def _run(self) -> str:
|
|
195
|
+
import json
|
|
196
|
+
|
|
197
|
+
result = self.client.balance()
|
|
198
|
+
return json.dumps(result, indent=2)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ProvisionStackTopupTool(BaseTool):
|
|
202
|
+
"""Add credits to your account."""
|
|
203
|
+
|
|
204
|
+
name: str = "provision_stack_topup"
|
|
205
|
+
description: str = f"Add credits to your {PRODUCT_NAME} account."
|
|
206
|
+
args_schema: Type[BaseModel] = TopupInput
|
|
207
|
+
client: ProvisionStackClient
|
|
208
|
+
|
|
209
|
+
def _run(self, amount_usd: float, rail: str = "stripe") -> str:
|
|
210
|
+
import json
|
|
211
|
+
|
|
212
|
+
if rail == "xrp":
|
|
213
|
+
result = self.client.topup_xrp(amount_usd)
|
|
214
|
+
else:
|
|
215
|
+
result = self.client.topup_stripe(amount_usd)
|
|
216
|
+
return json.dumps(result, indent=2)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_provision_stack_tools(
|
|
220
|
+
api_key: str | None = None,
|
|
221
|
+
base_url: str | None = None,
|
|
222
|
+
sandbox: bool = False,
|
|
223
|
+
) -> list[BaseTool]:
|
|
224
|
+
"""
|
|
225
|
+
Get all Provision Stack tools for CrewAI.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
api_key : str, optional
|
|
230
|
+
API token. Falls back to PROVISION_STACK_API_KEY env var.
|
|
231
|
+
base_url : str, optional
|
|
232
|
+
API base URL. Falls back to env var or default.
|
|
233
|
+
sandbox : bool
|
|
234
|
+
If True, use sandbox (mock Terraform, no real deploys).
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
list[BaseTool]
|
|
239
|
+
CrewAI Tool instances ready to assign to an agent.
|
|
240
|
+
"""
|
|
241
|
+
client = ProvisionStackClient(
|
|
242
|
+
api_key=api_key, base_url=base_url, sandbox=sandbox
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return [
|
|
246
|
+
ProvisionStackSuggestTool(client=client),
|
|
247
|
+
ProvisionStackDeployTool(client=client),
|
|
248
|
+
ProvisionStackStatusTool(client=client),
|
|
249
|
+
ProvisionStackDestroyTool(client=client),
|
|
250
|
+
ProvisionStackBalanceTool(client=client),
|
|
251
|
+
ProvisionStackTopupTool(client=client),
|
|
252
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "provision-stack-crewai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CrewAI tools for Provision Stack — deploy cloud infrastructure via AI agents."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Provision Stack" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"crewai",
|
|
17
|
+
"ai-agents",
|
|
18
|
+
"infrastructure",
|
|
19
|
+
"aws",
|
|
20
|
+
"gcp",
|
|
21
|
+
"mcp",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"provision-stack>=0.1.0",
|
|
36
|
+
"crewai>=0.100.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://provision-stack.com"
|
|
41
|
+
Documentation = "https://docs.provision-stack.com"
|
|
42
|
+
Repository = "https://github.com/TravisLinkey/provision-stack"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"pytest-mock>=3.12",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Tests for provision_stack_crewai.tools — CrewAI tool wrappers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import types
|
|
7
|
+
from typing import Any, Type
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ── Mock setup ────────────────────────────────────────────────────
|
|
12
|
+
# CrewAI's BaseTool uses Pydantic metaclass magic; we mock it
|
|
13
|
+
# to avoid pulling in crewai while keeping class inheritance.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MockBaseModel:
|
|
17
|
+
"""Stand-in for pydantic.BaseModel."""
|
|
18
|
+
model_config: dict[str, Any] = {}
|
|
19
|
+
|
|
20
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
21
|
+
for k, v in kwargs.items():
|
|
22
|
+
setattr(self, k, v)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mock_field(*args: Any, **kwargs: Any) -> Any:
|
|
26
|
+
return kwargs.get("default", args[0] if args else None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MockBaseTool:
|
|
30
|
+
"""Stand-in for crewai.tools.BaseTool — supports subclassing."""
|
|
31
|
+
|
|
32
|
+
name: str = ""
|
|
33
|
+
description: str = ""
|
|
34
|
+
args_schema: Type[MockBaseModel] | None = None
|
|
35
|
+
client: Any = None
|
|
36
|
+
|
|
37
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
38
|
+
for k, v in kwargs.items():
|
|
39
|
+
setattr(self, k, v)
|
|
40
|
+
|
|
41
|
+
def _run(self, **kwargs: Any) -> Any:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Install mocks before importing our modules
|
|
46
|
+
crewai = types.ModuleType("crewai")
|
|
47
|
+
crewai_tools = types.ModuleType("crewai.tools")
|
|
48
|
+
crewai_tools.BaseTool = MockBaseTool
|
|
49
|
+
|
|
50
|
+
pydantic = types.ModuleType("pydantic")
|
|
51
|
+
pydantic.BaseModel = MockBaseModel
|
|
52
|
+
pydantic.Field = mock_field
|
|
53
|
+
|
|
54
|
+
sys.modules.setdefault("crewai", crewai)
|
|
55
|
+
sys.modules.setdefault("crewai.tools", crewai_tools)
|
|
56
|
+
sys.modules.setdefault("pydantic", pydantic)
|
|
57
|
+
|
|
58
|
+
from provision_stack_crewai.tools import get_provision_stack_tools
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ── Tool factory ──────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestGetProvisionStackTools:
|
|
65
|
+
def test_returns_list(self):
|
|
66
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
67
|
+
assert isinstance(tools, list)
|
|
68
|
+
|
|
69
|
+
def test_returns_six_tools(self):
|
|
70
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
71
|
+
assert len(tools) == 6
|
|
72
|
+
|
|
73
|
+
def test_tool_names(self):
|
|
74
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
75
|
+
names = {t.name for t in tools}
|
|
76
|
+
assert names == {
|
|
77
|
+
"provision_stack_suggest",
|
|
78
|
+
"provision_stack_deploy",
|
|
79
|
+
"provision_stack_status",
|
|
80
|
+
"provision_stack_destroy",
|
|
81
|
+
"provision_stack_balance",
|
|
82
|
+
"provision_stack_topup",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def test_tools_have_descriptions(self):
|
|
86
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
87
|
+
for tool in tools:
|
|
88
|
+
assert tool.description
|
|
89
|
+
assert len(tool.description) > 10
|
|
90
|
+
|
|
91
|
+
def test_tools_have_args_schema(self):
|
|
92
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
93
|
+
# All tools except balance have an args_schema (balance takes no input)
|
|
94
|
+
for tool in tools:
|
|
95
|
+
if tool.name == "provision_stack_balance":
|
|
96
|
+
assert tool.args_schema is None
|
|
97
|
+
else:
|
|
98
|
+
assert tool.args_schema is not None
|
|
99
|
+
|
|
100
|
+
def test_tools_share_client(self):
|
|
101
|
+
tools = get_provision_stack_tools(api_key="test")
|
|
102
|
+
clients = {id(t.client) for t in tools}
|
|
103
|
+
assert len(clients) == 1
|
|
104
|
+
|
|
105
|
+
def test_sandbox_mode(self):
|
|
106
|
+
from provision_stack.config import SANDBOX_URL
|
|
107
|
+
|
|
108
|
+
tools = get_provision_stack_tools(api_key="test", sandbox=True)
|
|
109
|
+
assert tools[0].client.base_url == SANDBOX_URL
|
|
110
|
+
|
|
111
|
+
def test_custom_base_url(self):
|
|
112
|
+
tools = get_provision_stack_tools(
|
|
113
|
+
api_key="test", base_url="https://custom.api.com"
|
|
114
|
+
)
|
|
115
|
+
assert tools[0].client.base_url == "https://custom.api.com"
|