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 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.
@@ -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
+
@@ -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)