strot-ai 0.1.1__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.
- strot_ai-0.1.1/LICENSE +21 -0
- strot_ai-0.1.1/PKG-INFO +202 -0
- strot_ai-0.1.1/README.md +172 -0
- strot_ai-0.1.1/pyproject.toml +42 -0
- strot_ai-0.1.1/src/strot_cli/__init__.py +1 -0
- strot_ai-0.1.1/src/strot_cli/auth.py +36 -0
- strot_ai-0.1.1/src/strot_cli/commands/__init__.py +1 -0
- strot_ai-0.1.1/src/strot_cli/commands/deploy.py +219 -0
- strot_ai-0.1.1/src/strot_cli/commands/init.py +505 -0
- strot_ai-0.1.1/src/strot_cli/commands/login.py +93 -0
- strot_ai-0.1.1/src/strot_cli/commands/logout.py +27 -0
- strot_ai-0.1.1/src/strot_cli/commands/resources.py +102 -0
- strot_ai-0.1.1/src/strot_cli/commands/test.py +266 -0
- strot_ai-0.1.1/src/strot_cli/commands/whoami.py +40 -0
- strot_ai-0.1.1/src/strot_cli/main.py +50 -0
- strot_ai-0.1.1/src/strot_cli/project.py +86 -0
- strot_ai-0.1.1/src/strot_sdk/__init__.py +91 -0
- strot_ai-0.1.1/src/strot_sdk/ai.py +171 -0
- strot_ai-0.1.1/src/strot_sdk/client.py +585 -0
- strot_ai-0.1.1/src/strot_sdk/config.py +161 -0
- strot_ai-0.1.1/src/strot_sdk/cortex.py +416 -0
- strot_ai-0.1.1/src/strot_sdk/data.py +115 -0
- strot_ai-0.1.1/src/strot_sdk/decorators.py +200 -0
- strot_ai-0.1.1/src/strot_sdk/destinations.py +134 -0
- strot_ai-0.1.1/src/strot_sdk/pages.py +363 -0
- strot_ai-0.1.1/src/strot_sdk/registry.py +269 -0
- strot_ai-0.1.1/src/strot_sdk/types.py +58 -0
- strot_ai-0.1.1/src/strot_sdk/validation.py +154 -0
strot_ai-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 STROT AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
strot_ai-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strot-ai
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Build tools, agents, and pipelines for your STROT instance
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: strot,ai,agents,pipelines
|
|
8
|
+
Author: STROT AI
|
|
9
|
+
Author-email: dev@strot.ai
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Dist: click (>=8.0)
|
|
23
|
+
Requires-Dist: pyyaml (>=6.0)
|
|
24
|
+
Requires-Dist: requests (>=2.28)
|
|
25
|
+
Requires-Dist: rich (>=13.0)
|
|
26
|
+
Project-URL: Homepage, https://strot.ai
|
|
27
|
+
Project-URL: Repository, https://github.com/strot-ai/strot-sdk
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# STROT
|
|
31
|
+
|
|
32
|
+
Build tools, agents, pipelines, and dashboards for your [STROT](https://strot.ai) instance — in Python.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install strot-ai
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
strot login # Authenticate
|
|
44
|
+
strot init tool my-calculator # Scaffold a project
|
|
45
|
+
cd my-calculator # Edit main.py with any editor
|
|
46
|
+
strot test # Validate locally
|
|
47
|
+
strot deploy # Ship to your STROT instance
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## SDK Reference
|
|
51
|
+
|
|
52
|
+
### Tools (`@function`)
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from strot_sdk import function, llm
|
|
56
|
+
|
|
57
|
+
@function(
|
|
58
|
+
name='calculate_roi',
|
|
59
|
+
description='Calculate return on investment',
|
|
60
|
+
category='finance',
|
|
61
|
+
parameters=[
|
|
62
|
+
{'name': 'cost', 'type': 'number', 'description': 'Total cost'},
|
|
63
|
+
{'name': 'revenue', 'type': 'number', 'description': 'Total revenue'},
|
|
64
|
+
],
|
|
65
|
+
returns={'type': 'number', 'description': 'ROI percentage'}
|
|
66
|
+
)
|
|
67
|
+
class CalculateROI:
|
|
68
|
+
def run(self, cost: float, revenue: float) -> float:
|
|
69
|
+
return ((revenue - cost) / cost) * 100
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Agents (`@agent`)
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from strot_sdk import agent
|
|
76
|
+
|
|
77
|
+
@agent(
|
|
78
|
+
name='sales_analyst',
|
|
79
|
+
description='Analyzes sales data and provides insights',
|
|
80
|
+
tools=['calculate_roi', 'top_n'],
|
|
81
|
+
model='gpt-4o',
|
|
82
|
+
temperature=0.1,
|
|
83
|
+
)
|
|
84
|
+
class SalesAnalyst:
|
|
85
|
+
system_prompt = """You are a sales analyst.
|
|
86
|
+
Analyze data and provide actionable recommendations."""
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Cortex Pipelines (`@cortex`)
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from strot_sdk import cortex
|
|
93
|
+
from strot_sdk.cortex import Flow
|
|
94
|
+
|
|
95
|
+
@cortex(name='daily_etl', description='Daily ETL pipeline')
|
|
96
|
+
class DailyETL:
|
|
97
|
+
def build(self, flow: Flow):
|
|
98
|
+
data = flow.data_connector('load_sales', query_id=42)
|
|
99
|
+
cleaned = flow.transform(data, prompt='Clean and normalize the data')
|
|
100
|
+
flow.publish(cleaned, name='daily_report', destination='slack', channel='#data')
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Pages / Dashboards (`@page`)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from strot_sdk import page
|
|
107
|
+
from strot_sdk.pages import Dashboard, Row, KPI, Chart, Table
|
|
108
|
+
|
|
109
|
+
@page(name='sales_dashboard', description='Sales overview', type='dashboard')
|
|
110
|
+
class SalesDashboard:
|
|
111
|
+
def layout(self):
|
|
112
|
+
return Dashboard(
|
|
113
|
+
Row(
|
|
114
|
+
KPI(query_id=1, label='Revenue', format='currency'),
|
|
115
|
+
KPI(query_id=2, label='Orders'),
|
|
116
|
+
KPI(query_id=3, label='Customers'),
|
|
117
|
+
),
|
|
118
|
+
Row(
|
|
119
|
+
Chart(query_id=5, type='line', title='Revenue Trend', span=8),
|
|
120
|
+
Chart(query_id=6, type='donut', title='By Region', span=4),
|
|
121
|
+
),
|
|
122
|
+
Row(
|
|
123
|
+
Table(query_id=7, title='Recent Orders', sortable=True),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### LLM
|
|
129
|
+
|
|
130
|
+
All LLM calls go through your STROT instance — no API keys needed in your code.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from strot_sdk import llm
|
|
134
|
+
|
|
135
|
+
result = llm.complete("Summarize this: " + text)
|
|
136
|
+
result = llm.chat([{"role": "user", "content": "What is 2+2?"}])
|
|
137
|
+
category = llm.classify("Great product!", ["positive", "negative", "neutral"])
|
|
138
|
+
data = llm.extract("John is 30 years old", {"name": "string", "age": "number"})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Data Access
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from strot_sdk import strot, query, query_one
|
|
145
|
+
|
|
146
|
+
rows = strot.queries['monthly_sales'].execute()
|
|
147
|
+
rows = query("SELECT * FROM users", data_source_id=1)
|
|
148
|
+
row = query_one("SELECT * FROM users WHERE id = 1", data_source_id=1)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Destinations
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from strot_sdk import email, slack, webhook
|
|
155
|
+
|
|
156
|
+
email.send(to="team@example.com", subject="Report Ready", body="Done.")
|
|
157
|
+
slack.send(channel="#alerts", message="New alert!")
|
|
158
|
+
webhook.post(url="https://api.example.com/hook", data={"event": "deploy"})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## CLI Reference
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
strot login # Authenticate
|
|
165
|
+
strot login --token sk_live_abc123 # Direct API key
|
|
166
|
+
strot whoami # Show current user/org
|
|
167
|
+
strot logout # Clear credentials
|
|
168
|
+
|
|
169
|
+
strot init tool my-calculator # Scaffold tool
|
|
170
|
+
strot init agent my-analyst # Scaffold agent
|
|
171
|
+
strot init cortex my-pipeline # Scaffold pipeline
|
|
172
|
+
strot init page my-dashboard # Scaffold dashboard
|
|
173
|
+
|
|
174
|
+
strot test # Validate locally
|
|
175
|
+
strot deploy # Deploy to STROT
|
|
176
|
+
strot deploy --dry-run # Validate without deploying
|
|
177
|
+
|
|
178
|
+
strot resources # List all resources
|
|
179
|
+
strot resources queries # List saved queries
|
|
180
|
+
strot resources datasources # List data sources
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
Credentials stored in `~/.strot/credentials`:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
version: 1
|
|
189
|
+
current_profile: default
|
|
190
|
+
profiles:
|
|
191
|
+
default:
|
|
192
|
+
url: https://app.strot.ai
|
|
193
|
+
api_key: sk_live_abc123
|
|
194
|
+
org: 98bf9a0a-c9cd-42a8-9ea4-4f7fee9a4535
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Priority:** Constructor args > Environment variables (`STROT_URL`, `STROT_API_KEY`) > Credentials file
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
202
|
+
|
strot_ai-0.1.1/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# STROT
|
|
2
|
+
|
|
3
|
+
Build tools, agents, pipelines, and dashboards for your [STROT](https://strot.ai) instance — in Python.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install strot-ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
strot login # Authenticate
|
|
15
|
+
strot init tool my-calculator # Scaffold a project
|
|
16
|
+
cd my-calculator # Edit main.py with any editor
|
|
17
|
+
strot test # Validate locally
|
|
18
|
+
strot deploy # Ship to your STROT instance
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## SDK Reference
|
|
22
|
+
|
|
23
|
+
### Tools (`@function`)
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from strot_sdk import function, llm
|
|
27
|
+
|
|
28
|
+
@function(
|
|
29
|
+
name='calculate_roi',
|
|
30
|
+
description='Calculate return on investment',
|
|
31
|
+
category='finance',
|
|
32
|
+
parameters=[
|
|
33
|
+
{'name': 'cost', 'type': 'number', 'description': 'Total cost'},
|
|
34
|
+
{'name': 'revenue', 'type': 'number', 'description': 'Total revenue'},
|
|
35
|
+
],
|
|
36
|
+
returns={'type': 'number', 'description': 'ROI percentage'}
|
|
37
|
+
)
|
|
38
|
+
class CalculateROI:
|
|
39
|
+
def run(self, cost: float, revenue: float) -> float:
|
|
40
|
+
return ((revenue - cost) / cost) * 100
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Agents (`@agent`)
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from strot_sdk import agent
|
|
47
|
+
|
|
48
|
+
@agent(
|
|
49
|
+
name='sales_analyst',
|
|
50
|
+
description='Analyzes sales data and provides insights',
|
|
51
|
+
tools=['calculate_roi', 'top_n'],
|
|
52
|
+
model='gpt-4o',
|
|
53
|
+
temperature=0.1,
|
|
54
|
+
)
|
|
55
|
+
class SalesAnalyst:
|
|
56
|
+
system_prompt = """You are a sales analyst.
|
|
57
|
+
Analyze data and provide actionable recommendations."""
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Cortex Pipelines (`@cortex`)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from strot_sdk import cortex
|
|
64
|
+
from strot_sdk.cortex import Flow
|
|
65
|
+
|
|
66
|
+
@cortex(name='daily_etl', description='Daily ETL pipeline')
|
|
67
|
+
class DailyETL:
|
|
68
|
+
def build(self, flow: Flow):
|
|
69
|
+
data = flow.data_connector('load_sales', query_id=42)
|
|
70
|
+
cleaned = flow.transform(data, prompt='Clean and normalize the data')
|
|
71
|
+
flow.publish(cleaned, name='daily_report', destination='slack', channel='#data')
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Pages / Dashboards (`@page`)
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from strot_sdk import page
|
|
78
|
+
from strot_sdk.pages import Dashboard, Row, KPI, Chart, Table
|
|
79
|
+
|
|
80
|
+
@page(name='sales_dashboard', description='Sales overview', type='dashboard')
|
|
81
|
+
class SalesDashboard:
|
|
82
|
+
def layout(self):
|
|
83
|
+
return Dashboard(
|
|
84
|
+
Row(
|
|
85
|
+
KPI(query_id=1, label='Revenue', format='currency'),
|
|
86
|
+
KPI(query_id=2, label='Orders'),
|
|
87
|
+
KPI(query_id=3, label='Customers'),
|
|
88
|
+
),
|
|
89
|
+
Row(
|
|
90
|
+
Chart(query_id=5, type='line', title='Revenue Trend', span=8),
|
|
91
|
+
Chart(query_id=6, type='donut', title='By Region', span=4),
|
|
92
|
+
),
|
|
93
|
+
Row(
|
|
94
|
+
Table(query_id=7, title='Recent Orders', sortable=True),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### LLM
|
|
100
|
+
|
|
101
|
+
All LLM calls go through your STROT instance — no API keys needed in your code.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from strot_sdk import llm
|
|
105
|
+
|
|
106
|
+
result = llm.complete("Summarize this: " + text)
|
|
107
|
+
result = llm.chat([{"role": "user", "content": "What is 2+2?"}])
|
|
108
|
+
category = llm.classify("Great product!", ["positive", "negative", "neutral"])
|
|
109
|
+
data = llm.extract("John is 30 years old", {"name": "string", "age": "number"})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Data Access
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from strot_sdk import strot, query, query_one
|
|
116
|
+
|
|
117
|
+
rows = strot.queries['monthly_sales'].execute()
|
|
118
|
+
rows = query("SELECT * FROM users", data_source_id=1)
|
|
119
|
+
row = query_one("SELECT * FROM users WHERE id = 1", data_source_id=1)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Destinations
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from strot_sdk import email, slack, webhook
|
|
126
|
+
|
|
127
|
+
email.send(to="team@example.com", subject="Report Ready", body="Done.")
|
|
128
|
+
slack.send(channel="#alerts", message="New alert!")
|
|
129
|
+
webhook.post(url="https://api.example.com/hook", data={"event": "deploy"})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## CLI Reference
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
strot login # Authenticate
|
|
136
|
+
strot login --token sk_live_abc123 # Direct API key
|
|
137
|
+
strot whoami # Show current user/org
|
|
138
|
+
strot logout # Clear credentials
|
|
139
|
+
|
|
140
|
+
strot init tool my-calculator # Scaffold tool
|
|
141
|
+
strot init agent my-analyst # Scaffold agent
|
|
142
|
+
strot init cortex my-pipeline # Scaffold pipeline
|
|
143
|
+
strot init page my-dashboard # Scaffold dashboard
|
|
144
|
+
|
|
145
|
+
strot test # Validate locally
|
|
146
|
+
strot deploy # Deploy to STROT
|
|
147
|
+
strot deploy --dry-run # Validate without deploying
|
|
148
|
+
|
|
149
|
+
strot resources # List all resources
|
|
150
|
+
strot resources queries # List saved queries
|
|
151
|
+
strot resources datasources # List data sources
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
Credentials stored in `~/.strot/credentials`:
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
version: 1
|
|
160
|
+
current_profile: default
|
|
161
|
+
profiles:
|
|
162
|
+
default:
|
|
163
|
+
url: https://app.strot.ai
|
|
164
|
+
api_key: sk_live_abc123
|
|
165
|
+
org: 98bf9a0a-c9cd-42a8-9ea4-4f7fee9a4535
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Priority:** Constructor args > Environment variables (`STROT_URL`, `STROT_API_KEY`) > Credentials file
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "strot-ai"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Build tools, agents, and pipelines for your STROT instance"
|
|
5
|
+
authors = ["STROT AI <dev@strot.ai>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://strot.ai"
|
|
9
|
+
repository = "https://github.com/strot-ai/strot-sdk"
|
|
10
|
+
keywords = ["strot", "ai", "agents", "pipelines"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Topic :: Software Development :: Libraries",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
]
|
|
17
|
+
packages = [
|
|
18
|
+
{ include = "strot_sdk", from = "src" },
|
|
19
|
+
{ include = "strot_cli", from = "src" },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.poetry.dependencies]
|
|
23
|
+
python = ">=3.9,<4.0"
|
|
24
|
+
requests = ">=2.28"
|
|
25
|
+
pyyaml = ">=6.0"
|
|
26
|
+
click = ">=8.0"
|
|
27
|
+
rich = ">=13.0"
|
|
28
|
+
|
|
29
|
+
[tool.poetry.group.dev.dependencies]
|
|
30
|
+
pytest = ">=7.0"
|
|
31
|
+
pytest-cov = "*"
|
|
32
|
+
responses = ">=0.23"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.scripts]
|
|
35
|
+
strot = "strot_cli.main:cli"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["poetry-core"]
|
|
39
|
+
build-backend = "poetry.core.masonry.api"
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""STROT CLI — Command-line interface for the STROT SDK."""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
STROT CLI — Browser-Based Authentication (Org-Scoped)
|
|
3
|
+
|
|
4
|
+
Flow:
|
|
5
|
+
1. CLI opens browser to /<org_id>/cli/auth
|
|
6
|
+
2. User logs in if needed, clicks "Authorize CLI"
|
|
7
|
+
3. Browser shows the generated API key
|
|
8
|
+
4. User copies and pastes into the CLI prompt
|
|
9
|
+
5. CLI validates and saves to ~/.strot/credentials
|
|
10
|
+
"""
|
|
11
|
+
import secrets
|
|
12
|
+
import webbrowser
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_auth_code() -> str:
|
|
19
|
+
"""Generate a random code (used as a nonce in the auth URL)."""
|
|
20
|
+
return secrets.token_urlsafe(16)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_auth_url(instance_url: str, org_id: str, code: str) -> str:
|
|
24
|
+
"""Build the org-scoped browser auth URL."""
|
|
25
|
+
base = instance_url.rstrip("/")
|
|
26
|
+
return f"{base}/{org_id}/cli/auth?code={code}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def open_browser_auth(instance_url: str, org_id: str, code: str) -> str:
|
|
30
|
+
"""Open the browser to the auth URL. Returns the URL."""
|
|
31
|
+
url = get_auth_url(instance_url, org_id, code)
|
|
32
|
+
try:
|
|
33
|
+
webbrowser.open(url)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass # Browser open may fail in headless environments
|
|
36
|
+
return url
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""STROT CLI commands."""
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""strot deploy — Deploy project to a STROT instance."""
|
|
2
|
+
import click
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.option("--dry-run", is_flag=True, help="Validate without deploying")
|
|
11
|
+
def deploy(dry_run):
|
|
12
|
+
"""Deploy the current project to your STROT instance.
|
|
13
|
+
|
|
14
|
+
Reads strot.yaml, bundles files, and deploys to the connected instance.
|
|
15
|
+
"""
|
|
16
|
+
from strot_cli.project import find_project_root, load_project_config, read_project_files
|
|
17
|
+
|
|
18
|
+
root = find_project_root()
|
|
19
|
+
if not root:
|
|
20
|
+
console.print("[red]No strot.yaml found.[/red] Run 'strot init' to create a project.")
|
|
21
|
+
raise SystemExit(1)
|
|
22
|
+
|
|
23
|
+
config = load_project_config(root)
|
|
24
|
+
project_type = config["type"]
|
|
25
|
+
|
|
26
|
+
console.print(f"[bold]Deploying:[/bold] {config['name']} ({project_type})")
|
|
27
|
+
|
|
28
|
+
if project_type in ("cortex", "page"):
|
|
29
|
+
_deploy_compiled(root, config, dry_run)
|
|
30
|
+
else:
|
|
31
|
+
_deploy_function(root, config, dry_run)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _deploy_function(root, config, dry_run):
|
|
35
|
+
"""Deploy a tool or agent (code-based)."""
|
|
36
|
+
from strot_cli.project import read_project_files
|
|
37
|
+
|
|
38
|
+
files = read_project_files(root)
|
|
39
|
+
console.print(f"[dim]Files: {', '.join(files.keys())}[/dim]")
|
|
40
|
+
|
|
41
|
+
if not files:
|
|
42
|
+
console.print("[red]No files to deploy.[/red]")
|
|
43
|
+
raise SystemExit(1)
|
|
44
|
+
|
|
45
|
+
entry = config["entry"]
|
|
46
|
+
if entry not in files:
|
|
47
|
+
console.print(f"[red]Entry file '{entry}' not found.[/red]")
|
|
48
|
+
raise SystemExit(1)
|
|
49
|
+
|
|
50
|
+
code = files.pop(entry)
|
|
51
|
+
|
|
52
|
+
if dry_run:
|
|
53
|
+
console.print()
|
|
54
|
+
console.print("[green]Dry run passed.[/green] Project is valid for deployment.")
|
|
55
|
+
console.print(f" Name: {config['name']}")
|
|
56
|
+
console.print(f" Type: {config['type']}")
|
|
57
|
+
console.print(f" Entry: {entry} ({len(code)} chars)")
|
|
58
|
+
if files:
|
|
59
|
+
console.print(f" Additional files: {', '.join(files.keys())}")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
from strot_sdk.client import StrotClient
|
|
64
|
+
client = StrotClient()
|
|
65
|
+
|
|
66
|
+
result = client.deploy_function(
|
|
67
|
+
name=config["name"],
|
|
68
|
+
code=code,
|
|
69
|
+
function_type=config["type"],
|
|
70
|
+
description=config.get("description", ""),
|
|
71
|
+
category=config.get("category", "custom"),
|
|
72
|
+
language=config.get("language", "python"),
|
|
73
|
+
file_contents=files if files else None,
|
|
74
|
+
config={
|
|
75
|
+
"version": config.get("version", "1.0.0"),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if result.success:
|
|
80
|
+
console.print()
|
|
81
|
+
console.print(f"[green]Deployed successfully![/green] ({result.action})")
|
|
82
|
+
if result.url:
|
|
83
|
+
console.print(f"[dim]URL: {result.url}[/dim]")
|
|
84
|
+
if result.id:
|
|
85
|
+
console.print(f"[dim]ID: {result.id}[/dim]")
|
|
86
|
+
else:
|
|
87
|
+
console.print(f"[red]Deploy failed:[/red] {result.error}")
|
|
88
|
+
raise SystemExit(1)
|
|
89
|
+
|
|
90
|
+
except SystemExit:
|
|
91
|
+
raise
|
|
92
|
+
except Exception as e:
|
|
93
|
+
console.print(f"[red]Deploy failed:[/red] {e}")
|
|
94
|
+
raise SystemExit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _deploy_compiled(root, config, dry_run):
|
|
98
|
+
"""Deploy a cortex pipeline or page (compiled to JSON DSL)."""
|
|
99
|
+
import sys
|
|
100
|
+
import importlib.util
|
|
101
|
+
|
|
102
|
+
entry = config["entry"]
|
|
103
|
+
entry_file = root / entry
|
|
104
|
+
|
|
105
|
+
if not entry_file.exists():
|
|
106
|
+
console.print(f"[red]Entry file '{entry}' not found.[/red]")
|
|
107
|
+
raise SystemExit(1)
|
|
108
|
+
|
|
109
|
+
# Import the entry module to find decorated classes
|
|
110
|
+
sys.path.insert(0, str(root))
|
|
111
|
+
try:
|
|
112
|
+
spec = importlib.util.spec_from_file_location("__strot_deploy__", entry_file)
|
|
113
|
+
module = importlib.util.module_from_spec(spec)
|
|
114
|
+
spec.loader.exec_module(module)
|
|
115
|
+
|
|
116
|
+
project_type = config["type"]
|
|
117
|
+
|
|
118
|
+
if project_type == "cortex":
|
|
119
|
+
dsl = _compile_cortex(module, config)
|
|
120
|
+
else:
|
|
121
|
+
dsl = _compile_page(module, config)
|
|
122
|
+
|
|
123
|
+
if dry_run:
|
|
124
|
+
import json
|
|
125
|
+
console.print()
|
|
126
|
+
console.print("[green]Dry run passed.[/green] Compiled successfully.")
|
|
127
|
+
console.print(f" Name: {config['name']}")
|
|
128
|
+
console.print(f" Type: {project_type}")
|
|
129
|
+
console.print()
|
|
130
|
+
console.print("[dim]Compiled DSL:[/dim]")
|
|
131
|
+
console.print(json.dumps(dsl, indent=2))
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Deploy to STROT instance
|
|
135
|
+
from strot_sdk.client import StrotClient
|
|
136
|
+
client = StrotClient()
|
|
137
|
+
|
|
138
|
+
if project_type == "cortex":
|
|
139
|
+
result = client.deploy_orchestration(
|
|
140
|
+
name=config["name"],
|
|
141
|
+
dsl=dsl,
|
|
142
|
+
description=config.get("description", ""),
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
result = client.deploy_page(
|
|
146
|
+
name=config["name"],
|
|
147
|
+
layout=dsl,
|
|
148
|
+
description=config.get("description", ""),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if result.success:
|
|
152
|
+
console.print()
|
|
153
|
+
console.print(f"[green]Deployed successfully![/green] ({result.action})")
|
|
154
|
+
if result.url:
|
|
155
|
+
console.print(f"[dim]URL: {result.url}[/dim]")
|
|
156
|
+
if result.id:
|
|
157
|
+
console.print(f"[dim]ID: {result.id}[/dim]")
|
|
158
|
+
else:
|
|
159
|
+
console.print(f"[red]Deploy failed:[/red] {result.error}")
|
|
160
|
+
raise SystemExit(1)
|
|
161
|
+
|
|
162
|
+
except SystemExit:
|
|
163
|
+
raise
|
|
164
|
+
except Exception as e:
|
|
165
|
+
console.print(f"[red]Deploy failed:[/red] {e}")
|
|
166
|
+
import traceback
|
|
167
|
+
traceback.print_exc()
|
|
168
|
+
raise SystemExit(1)
|
|
169
|
+
finally:
|
|
170
|
+
sys.path.pop(0)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _compile_cortex(module, config):
|
|
174
|
+
"""Compile a @cortex decorated class to DSL."""
|
|
175
|
+
from strot_sdk.decorators import get_registry
|
|
176
|
+
from strot_sdk.cortex import build_pipeline
|
|
177
|
+
|
|
178
|
+
registry = get_registry()
|
|
179
|
+
cortex_items = registry.get("cortex", {})
|
|
180
|
+
|
|
181
|
+
if not cortex_items:
|
|
182
|
+
raise ValueError("No @cortex decorated class found in entry file.")
|
|
183
|
+
|
|
184
|
+
# Use the first (or matching) cortex class
|
|
185
|
+
target_name = config["name"]
|
|
186
|
+
cls = None
|
|
187
|
+
for name, entry in cortex_items.items():
|
|
188
|
+
if name == target_name:
|
|
189
|
+
cls = entry["class"]
|
|
190
|
+
break
|
|
191
|
+
if cls is None:
|
|
192
|
+
cls = next(iter(cortex_items.values()))["class"]
|
|
193
|
+
|
|
194
|
+
console.print(f"[dim]Compiling pipeline: {target_name}[/dim]")
|
|
195
|
+
return build_pipeline(cls)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _compile_page(module, config):
|
|
199
|
+
"""Compile a @page decorated class to layout JSON."""
|
|
200
|
+
from strot_sdk.decorators import get_registry
|
|
201
|
+
from strot_sdk.pages import build_page
|
|
202
|
+
|
|
203
|
+
registry = get_registry()
|
|
204
|
+
page_items = registry.get("page", {})
|
|
205
|
+
|
|
206
|
+
if not page_items:
|
|
207
|
+
raise ValueError("No @page decorated class found in entry file.")
|
|
208
|
+
|
|
209
|
+
target_name = config["name"]
|
|
210
|
+
cls = None
|
|
211
|
+
for name, entry in page_items.items():
|
|
212
|
+
if name == target_name:
|
|
213
|
+
cls = entry["class"]
|
|
214
|
+
break
|
|
215
|
+
if cls is None:
|
|
216
|
+
cls = next(iter(page_items.values()))["class"]
|
|
217
|
+
|
|
218
|
+
console.print(f"[dim]Compiling page layout: {target_name}[/dim]")
|
|
219
|
+
return build_page(cls)
|