cycls 0.0.2.40__tar.gz → 0.0.2.92__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,27 @@
1
+ ### AL ###
2
+ #Template for AL projects for Dynamics 365 Business Central
3
+ #launch.json folder
4
+ .vscode/
5
+ #Cache folder
6
+ .alcache/
7
+ #Symbols folder
8
+ .alpackages/
9
+ #Snapshots folder
10
+ .snapshots/
11
+ #Testing Output folder
12
+ .output/
13
+ #Extension App-file
14
+ *.app
15
+ #Rapid Application Development File
16
+ rad.json
17
+ #Translation Base-file
18
+ *.g.xlf
19
+ #License-file
20
+ *.flf
21
+ #Test results file
22
+ TestResults.xml
23
+ dist/
24
+ **/__pycache__/
25
+ .*
26
+ !.github
27
+ mj/
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: cycls
3
+ Version: 0.0.2.92
4
+ Summary: Distribute Intelligence
5
+ Author-email: "Mohammed J. AlRujayi" <mj@cycls.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: cloudpickle>=3.1.1
8
+ Requires-Dist: docker>=7.1.0
9
+ Requires-Dist: email-validator>=2.0.0
10
+ Requires-Dist: fastapi>=0.111.0
11
+ Requires-Dist: httpx>=0.27.0
12
+ Requires-Dist: pyjwt>=2.8.0
13
+ Requires-Dist: uvicorn>=0.30.0
14
+ Requires-Dist: watchfiles>=1.0.0
15
+ Provides-Extra: state
16
+ Requires-Dist: agentfs-sdk==0.4.0; extra == 'state'
17
+ Description-Content-Type: text/markdown
18
+
19
+ <h3 align="center">
20
+ Distribute Intelligence
21
+ </h3>
22
+
23
+ <h4 align="center">
24
+ <a href="https://cycls.com">Website</a> |
25
+ <a href="https://docs.cycls.com">Docs</a> |
26
+ <a href="docs/tutorial.md">Tutorial</a>
27
+ </h4>
28
+
29
+ <h4 align="center">
30
+ <a href="https://pypi.python.org/pypi/cycls"><img src="https://img.shields.io/pypi/v/cycls.svg?label=cycls+pypi&color=blueviolet" alt="cycls Python package on PyPi" /></a>
31
+ <a href="https://github.com/Cycls/cycls/actions/workflows/tests.yml"><img src="https://github.com/Cycls/cycls/actions/workflows/tests.yml/badge.svg" alt="Tests" /></a>
32
+ <a href="https://blog.cycls.com"><img src="https://img.shields.io/badge/newsletter-blueviolet.svg?logo=substack&label=cycls" alt="Cycls newsletter" /></a>
33
+ <a href="https://x.com/cyclsai">
34
+ <img src="https://img.shields.io/twitter/follow/CyclsAI" alt="Cycls Twitter" />
35
+ </a>
36
+ </h4>
37
+
38
+ ---
39
+
40
+ # Cycls
41
+
42
+ The open-source SDK for distributing AI agents.
43
+
44
+ ```
45
+ Agent extends App (prompts, skills)
46
+ └── App extends Function (web UI)
47
+ └── Function (containerization)
48
+ ```
49
+
50
+ ## Distribute Intelligence
51
+
52
+ Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
53
+
54
+ ```python
55
+ import cycls
56
+
57
+ cycls.api_key = "YOUR_CYCLS_API_KEY"
58
+
59
+ @cycls.app(pip=["openai"])
60
+ async def app(context):
61
+ from openai import AsyncOpenAI
62
+ client = AsyncOpenAI()
63
+
64
+ stream = await client.responses.create(
65
+ model="o3-mini",
66
+ input=context.messages,
67
+ stream=True,
68
+ reasoning={"effort": "medium", "summary": "auto"},
69
+ )
70
+
71
+ async for event in stream:
72
+ if event.type == "response.reasoning_summary_text.delta":
73
+ yield {"type": "thinking", "thinking": event.delta} # Renders as thinking bubble
74
+ elif event.type == "response.output_text.delta":
75
+ yield event.delta
76
+
77
+ app.deploy() # Live at https://agent.cycls.ai
78
+ ```
79
+
80
+ ## Installation
81
+
82
+ ```bash
83
+ pip install cycls
84
+ ```
85
+
86
+ Requires Docker. See the [full tutorial](docs/tutorial.md) for a comprehensive guide.
87
+
88
+ ## What You Get
89
+
90
+ - **Streaming API** - OpenAI-compatible `/chat/completions` endpoint
91
+ - **Web Interface** - Chat UI served automatically
92
+ - **Authentication** - `auth=True` enables JWT-based access control
93
+ - **Analytics** - `analytics=True` tracks usage
94
+ - **Monetization** - `plan="cycls_pass"` integrates with [Cycls Pass](https://cycls.ai) subscriptions
95
+ - **Native UI Components** - Render thinking bubbles, tables, code blocks in responses
96
+
97
+ ## Running
98
+
99
+ ```python
100
+ app.local() # Development with hot-reload (localhost:8080)
101
+ app.local(watch=False) # Development without hot-reload
102
+ app.deploy() # Production: https://agent.cycls.ai
103
+ ```
104
+
105
+ Get an API key at [cycls.com](https://cycls.com).
106
+
107
+ ## Authentication & Analytics
108
+
109
+ See the [tutorial](docs/tutorial.md#authentication) for full auth and monetization examples.
110
+
111
+ ```python
112
+ @cycls.app(pip=["openai"], auth=True, analytics=True)
113
+ async def app(context):
114
+ # context.user available when auth=True
115
+ user = context.user # User(id, email, name, plans)
116
+ yield f"Hello {user.name}!"
117
+ ```
118
+
119
+ | Flag | Description |
120
+ |------|-------------|
121
+ | `auth=True` | Universal user pool via Cycls Pass (Clerk-based). You can also use your own Clerk auth. |
122
+ | `analytics=True` | Rich usage metrics available on the Cycls dashboard. |
123
+ | `plan="cycls_pass"` | Monetization via Cycls Pass subscriptions. Enables both auth and analytics. |
124
+
125
+ ## Native UI Components
126
+
127
+ Yield structured objects for rich streaming responses. See the [tutorial](docs/tutorial.md#native-ui-components) for all component types and examples.
128
+
129
+ ```python
130
+ @cycls.app()
131
+ async def demo(context):
132
+ yield {"type": "thinking", "thinking": "Analyzing the request..."}
133
+ yield "Here's what I found:\n\n"
134
+
135
+ yield {"type": "table", "headers": ["Name", "Status"]}
136
+ yield {"type": "table", "row": ["Server 1", "Online"]}
137
+ yield {"type": "table", "row": ["Server 2", "Offline"]}
138
+
139
+ yield {"type": "code", "code": "result = analyze(data)", "language": "python"}
140
+ yield {"type": "callout", "callout": "Analysis complete!", "style": "success"}
141
+ ```
142
+
143
+ | Component | Streaming |
144
+ |-----------|-----------|
145
+ | `{"type": "thinking", "thinking": "..."}` | Yes |
146
+ | `{"type": "code", "code": "...", "language": "..."}` | Yes |
147
+ | `{"type": "table", "headers": [...]}` | Yes |
148
+ | `{"type": "table", "row": [...]}` | Yes |
149
+ | `{"type": "status", "status": "..."}` | Yes |
150
+ | `{"type": "callout", "callout": "...", "style": "..."}` | Yes |
151
+ | `{"type": "image", "src": "..."}` | Yes |
152
+
153
+ ### Thinking Bubbles
154
+
155
+ The `{"type": "thinking", "thinking": "..."}` component renders as a collapsible thinking bubble in the UI. Each yield appends to the same bubble until a different component type is yielded:
156
+
157
+ ```python
158
+ # Multiple yields build one thinking bubble
159
+ yield {"type": "thinking", "thinking": "Let me "}
160
+ yield {"type": "thinking", "thinking": "analyze this..."}
161
+ yield {"type": "thinking", "thinking": " Done thinking."}
162
+
163
+ # Then output the response
164
+ yield "Here's what I found..."
165
+ ```
166
+
167
+ This works seamlessly with OpenAI's reasoning models - just map reasoning summaries to the thinking component.
168
+
169
+ ## Context Object
170
+
171
+ ```python
172
+ @cycls.app()
173
+ async def chat(context):
174
+ context.messages # [{"role": "user", "content": "..."}]
175
+ context.messages.raw # Full data including UI component parts
176
+ context.user # User(id, email, name, plans) when auth=True
177
+ ```
178
+
179
+ ## API Endpoints
180
+
181
+ | Endpoint | Format |
182
+ |----------|--------|
183
+ | `POST chat/cycls` | Cycls streaming protocol |
184
+ | `POST chat/completions` | OpenAI-compatible |
185
+
186
+ ## Streaming Protocol
187
+
188
+ Cycls streams structured components over SSE:
189
+
190
+ ```
191
+ data: {"type": "thinking", "thinking": "Let me "}
192
+ data: {"type": "thinking", "thinking": "analyze..."}
193
+ data: {"type": "text", "text": "Here's the answer"}
194
+ data: {"type": "callout", "callout": "Done!", "style": "success"}
195
+ data: [DONE]
196
+ ```
197
+
198
+ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integration.
199
+
200
+ ## Declarative Infrastructure
201
+
202
+ Define your entire runtime in the decorator. See the [tutorial](docs/tutorial.md#declarative-infrastructure) for more details.
203
+
204
+ ```python
205
+ @cycls.app(
206
+ pip=["openai", "pandas", "numpy"],
207
+ apt=["ffmpeg", "libmagic1"],
208
+ copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
209
+ copy_public=["./assets/logo.png", "./static/"],
210
+ )
211
+ async def my_app(context):
212
+ ...
213
+ ```
214
+
215
+ ### `pip` - Python Packages
216
+
217
+ Install any packages from PyPI. These are installed during the container build.
218
+
219
+ ```python
220
+ pip=["openai", "pandas", "numpy", "transformers"]
221
+ ```
222
+
223
+ ### `apt` - System Packages
224
+
225
+ Install system-level dependencies via apt-get. Need ffmpeg for audio processing? ImageMagick for images? Just declare it.
226
+
227
+ ```python
228
+ apt=["ffmpeg", "imagemagick", "libpq-dev"]
229
+ ```
230
+
231
+ ### `copy` - Bundle Files and Directories
232
+
233
+ Include local files and directories in your container. Works with both relative and absolute paths. Copies files and entire directory trees.
234
+
235
+ ```python
236
+ copy=[
237
+ "./utils.py", # Single file, relative path
238
+ "./models/", # Entire directory
239
+ "/home/user/configs/app.json", # Absolute path
240
+ ]
241
+ ```
242
+
243
+ Then import them in your function:
244
+
245
+ ```python
246
+ @cycls.app(copy=["./utils.py"])
247
+ async def chat(context):
248
+ from utils import helper_function # Your bundled module
249
+ ...
250
+ ```
251
+
252
+ ### `copy_public` - Static Files
253
+
254
+ Files and directories served at the `/public` endpoint. Perfect for images, downloads, or any static assets your agent needs to reference.
255
+
256
+ ```python
257
+ copy_public=["./assets/logo.png", "./downloads/"]
258
+ ```
259
+
260
+ Access them at `https://your-app.cycls.ai/public/logo.png`.
261
+
262
+ ---
263
+
264
+ ### What You Get
265
+
266
+ - **One file** - Code, dependencies, configuration, and infrastructure together
267
+ - **Instant deploys** - Unchanged code deploys in seconds from cache
268
+ - **No drift** - What you see is what runs. Always.
269
+ - **Just works** - Closures, lambdas, dynamic imports - your function runs exactly as written
270
+
271
+ No YAML. No Dockerfiles. No infrastructure repo. The code is the deployment.
272
+
273
+ ## Learn More
274
+
275
+ - [Tutorial](docs/tutorial.md) - Comprehensive guide from basics to advanced
276
+ - [Streaming Protocol](docs/streaming-protocol.md) - Frontend integration
277
+ - [Runtime](docs/runtime.md) - Containerization details
278
+ - [Examples](examples/) - Working code samples
279
+
280
+ ## License
281
+
282
+ MIT
@@ -0,0 +1,264 @@
1
+ <h3 align="center">
2
+ Distribute Intelligence
3
+ </h3>
4
+
5
+ <h4 align="center">
6
+ <a href="https://cycls.com">Website</a> |
7
+ <a href="https://docs.cycls.com">Docs</a> |
8
+ <a href="docs/tutorial.md">Tutorial</a>
9
+ </h4>
10
+
11
+ <h4 align="center">
12
+ <a href="https://pypi.python.org/pypi/cycls"><img src="https://img.shields.io/pypi/v/cycls.svg?label=cycls+pypi&color=blueviolet" alt="cycls Python package on PyPi" /></a>
13
+ <a href="https://github.com/Cycls/cycls/actions/workflows/tests.yml"><img src="https://github.com/Cycls/cycls/actions/workflows/tests.yml/badge.svg" alt="Tests" /></a>
14
+ <a href="https://blog.cycls.com"><img src="https://img.shields.io/badge/newsletter-blueviolet.svg?logo=substack&label=cycls" alt="Cycls newsletter" /></a>
15
+ <a href="https://x.com/cyclsai">
16
+ <img src="https://img.shields.io/twitter/follow/CyclsAI" alt="Cycls Twitter" />
17
+ </a>
18
+ </h4>
19
+
20
+ ---
21
+
22
+ # Cycls
23
+
24
+ The open-source SDK for distributing AI agents.
25
+
26
+ ```
27
+ Agent extends App (prompts, skills)
28
+ └── App extends Function (web UI)
29
+ └── Function (containerization)
30
+ ```
31
+
32
+ ## Distribute Intelligence
33
+
34
+ Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
35
+
36
+ ```python
37
+ import cycls
38
+
39
+ cycls.api_key = "YOUR_CYCLS_API_KEY"
40
+
41
+ @cycls.app(pip=["openai"])
42
+ async def app(context):
43
+ from openai import AsyncOpenAI
44
+ client = AsyncOpenAI()
45
+
46
+ stream = await client.responses.create(
47
+ model="o3-mini",
48
+ input=context.messages,
49
+ stream=True,
50
+ reasoning={"effort": "medium", "summary": "auto"},
51
+ )
52
+
53
+ async for event in stream:
54
+ if event.type == "response.reasoning_summary_text.delta":
55
+ yield {"type": "thinking", "thinking": event.delta} # Renders as thinking bubble
56
+ elif event.type == "response.output_text.delta":
57
+ yield event.delta
58
+
59
+ app.deploy() # Live at https://agent.cycls.ai
60
+ ```
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install cycls
66
+ ```
67
+
68
+ Requires Docker. See the [full tutorial](docs/tutorial.md) for a comprehensive guide.
69
+
70
+ ## What You Get
71
+
72
+ - **Streaming API** - OpenAI-compatible `/chat/completions` endpoint
73
+ - **Web Interface** - Chat UI served automatically
74
+ - **Authentication** - `auth=True` enables JWT-based access control
75
+ - **Analytics** - `analytics=True` tracks usage
76
+ - **Monetization** - `plan="cycls_pass"` integrates with [Cycls Pass](https://cycls.ai) subscriptions
77
+ - **Native UI Components** - Render thinking bubbles, tables, code blocks in responses
78
+
79
+ ## Running
80
+
81
+ ```python
82
+ app.local() # Development with hot-reload (localhost:8080)
83
+ app.local(watch=False) # Development without hot-reload
84
+ app.deploy() # Production: https://agent.cycls.ai
85
+ ```
86
+
87
+ Get an API key at [cycls.com](https://cycls.com).
88
+
89
+ ## Authentication & Analytics
90
+
91
+ See the [tutorial](docs/tutorial.md#authentication) for full auth and monetization examples.
92
+
93
+ ```python
94
+ @cycls.app(pip=["openai"], auth=True, analytics=True)
95
+ async def app(context):
96
+ # context.user available when auth=True
97
+ user = context.user # User(id, email, name, plans)
98
+ yield f"Hello {user.name}!"
99
+ ```
100
+
101
+ | Flag | Description |
102
+ |------|-------------|
103
+ | `auth=True` | Universal user pool via Cycls Pass (Clerk-based). You can also use your own Clerk auth. |
104
+ | `analytics=True` | Rich usage metrics available on the Cycls dashboard. |
105
+ | `plan="cycls_pass"` | Monetization via Cycls Pass subscriptions. Enables both auth and analytics. |
106
+
107
+ ## Native UI Components
108
+
109
+ Yield structured objects for rich streaming responses. See the [tutorial](docs/tutorial.md#native-ui-components) for all component types and examples.
110
+
111
+ ```python
112
+ @cycls.app()
113
+ async def demo(context):
114
+ yield {"type": "thinking", "thinking": "Analyzing the request..."}
115
+ yield "Here's what I found:\n\n"
116
+
117
+ yield {"type": "table", "headers": ["Name", "Status"]}
118
+ yield {"type": "table", "row": ["Server 1", "Online"]}
119
+ yield {"type": "table", "row": ["Server 2", "Offline"]}
120
+
121
+ yield {"type": "code", "code": "result = analyze(data)", "language": "python"}
122
+ yield {"type": "callout", "callout": "Analysis complete!", "style": "success"}
123
+ ```
124
+
125
+ | Component | Streaming |
126
+ |-----------|-----------|
127
+ | `{"type": "thinking", "thinking": "..."}` | Yes |
128
+ | `{"type": "code", "code": "...", "language": "..."}` | Yes |
129
+ | `{"type": "table", "headers": [...]}` | Yes |
130
+ | `{"type": "table", "row": [...]}` | Yes |
131
+ | `{"type": "status", "status": "..."}` | Yes |
132
+ | `{"type": "callout", "callout": "...", "style": "..."}` | Yes |
133
+ | `{"type": "image", "src": "..."}` | Yes |
134
+
135
+ ### Thinking Bubbles
136
+
137
+ The `{"type": "thinking", "thinking": "..."}` component renders as a collapsible thinking bubble in the UI. Each yield appends to the same bubble until a different component type is yielded:
138
+
139
+ ```python
140
+ # Multiple yields build one thinking bubble
141
+ yield {"type": "thinking", "thinking": "Let me "}
142
+ yield {"type": "thinking", "thinking": "analyze this..."}
143
+ yield {"type": "thinking", "thinking": " Done thinking."}
144
+
145
+ # Then output the response
146
+ yield "Here's what I found..."
147
+ ```
148
+
149
+ This works seamlessly with OpenAI's reasoning models - just map reasoning summaries to the thinking component.
150
+
151
+ ## Context Object
152
+
153
+ ```python
154
+ @cycls.app()
155
+ async def chat(context):
156
+ context.messages # [{"role": "user", "content": "..."}]
157
+ context.messages.raw # Full data including UI component parts
158
+ context.user # User(id, email, name, plans) when auth=True
159
+ ```
160
+
161
+ ## API Endpoints
162
+
163
+ | Endpoint | Format |
164
+ |----------|--------|
165
+ | `POST chat/cycls` | Cycls streaming protocol |
166
+ | `POST chat/completions` | OpenAI-compatible |
167
+
168
+ ## Streaming Protocol
169
+
170
+ Cycls streams structured components over SSE:
171
+
172
+ ```
173
+ data: {"type": "thinking", "thinking": "Let me "}
174
+ data: {"type": "thinking", "thinking": "analyze..."}
175
+ data: {"type": "text", "text": "Here's the answer"}
176
+ data: {"type": "callout", "callout": "Done!", "style": "success"}
177
+ data: [DONE]
178
+ ```
179
+
180
+ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integration.
181
+
182
+ ## Declarative Infrastructure
183
+
184
+ Define your entire runtime in the decorator. See the [tutorial](docs/tutorial.md#declarative-infrastructure) for more details.
185
+
186
+ ```python
187
+ @cycls.app(
188
+ pip=["openai", "pandas", "numpy"],
189
+ apt=["ffmpeg", "libmagic1"],
190
+ copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
191
+ copy_public=["./assets/logo.png", "./static/"],
192
+ )
193
+ async def my_app(context):
194
+ ...
195
+ ```
196
+
197
+ ### `pip` - Python Packages
198
+
199
+ Install any packages from PyPI. These are installed during the container build.
200
+
201
+ ```python
202
+ pip=["openai", "pandas", "numpy", "transformers"]
203
+ ```
204
+
205
+ ### `apt` - System Packages
206
+
207
+ Install system-level dependencies via apt-get. Need ffmpeg for audio processing? ImageMagick for images? Just declare it.
208
+
209
+ ```python
210
+ apt=["ffmpeg", "imagemagick", "libpq-dev"]
211
+ ```
212
+
213
+ ### `copy` - Bundle Files and Directories
214
+
215
+ Include local files and directories in your container. Works with both relative and absolute paths. Copies files and entire directory trees.
216
+
217
+ ```python
218
+ copy=[
219
+ "./utils.py", # Single file, relative path
220
+ "./models/", # Entire directory
221
+ "/home/user/configs/app.json", # Absolute path
222
+ ]
223
+ ```
224
+
225
+ Then import them in your function:
226
+
227
+ ```python
228
+ @cycls.app(copy=["./utils.py"])
229
+ async def chat(context):
230
+ from utils import helper_function # Your bundled module
231
+ ...
232
+ ```
233
+
234
+ ### `copy_public` - Static Files
235
+
236
+ Files and directories served at the `/public` endpoint. Perfect for images, downloads, or any static assets your agent needs to reference.
237
+
238
+ ```python
239
+ copy_public=["./assets/logo.png", "./downloads/"]
240
+ ```
241
+
242
+ Access them at `https://your-app.cycls.ai/public/logo.png`.
243
+
244
+ ---
245
+
246
+ ### What You Get
247
+
248
+ - **One file** - Code, dependencies, configuration, and infrastructure together
249
+ - **Instant deploys** - Unchanged code deploys in seconds from cache
250
+ - **No drift** - What you see is what runs. Always.
251
+ - **Just works** - Closures, lambdas, dynamic imports - your function runs exactly as written
252
+
253
+ No YAML. No Dockerfiles. No infrastructure repo. The code is the deployment.
254
+
255
+ ## Learn More
256
+
257
+ - [Tutorial](docs/tutorial.md) - Comprehensive guide from basics to advanced
258
+ - [Streaming Protocol](docs/streaming-protocol.md) - Frontend integration
259
+ - [Runtime](docs/runtime.md) - Containerization details
260
+ - [Examples](examples/) - Working code samples
261
+
262
+ ## License
263
+
264
+ MIT
@@ -0,0 +1,14 @@
1
+ from . import function as _function_module
2
+ from .function import function, Function
3
+ from .app import app, App
4
+
5
+ def __getattr__(name):
6
+ if name in ("api_key", "base_url"):
7
+ return getattr(_function_module, name)
8
+ raise AttributeError(f"module 'cycls' has no attribute '{name}'")
9
+
10
+ def __setattr__(name, value):
11
+ if name in ("api_key", "base_url"):
12
+ setattr(_function_module, name, value)
13
+ else:
14
+ raise AttributeError(f"module 'cycls' has no attribute '{name}'")
@@ -0,0 +1,91 @@
1
+ import os
2
+ import uvicorn
3
+ import importlib.resources
4
+
5
+ from .function import Function, _get_api_key, _get_base_url
6
+ from .web import web, Config
7
+
8
+ CYCLS_PATH = importlib.resources.files('cycls')
9
+
10
+ THEMES = ["default", "dev"]
11
+
12
+
13
+ class App(Function):
14
+ """App extends Function with web UI serving capabilities."""
15
+
16
+ def __init__(self, func, name, theme="default", pip=None, apt=None, copy=None, copy_public=None,
17
+ auth=False, org=None, header=None, intro=None, title=None, plan="free", analytics=False,
18
+ state=False):
19
+ if theme not in THEMES:
20
+ raise ValueError(f"Unknown theme: {theme}. Available: {THEMES}")
21
+ self.user_func = func
22
+ self.theme = theme
23
+ self.copy_public = copy_public or []
24
+ self.state = state
25
+
26
+ self.config = Config(
27
+ header=header,
28
+ intro=intro,
29
+ title=title,
30
+ auth=auth,
31
+ plan=plan,
32
+ analytics=analytics,
33
+ org=org,
34
+ state=state,
35
+ )
36
+
37
+ # Build files dict for Function (theme is inside cycls/)
38
+ files = {str(CYCLS_PATH): "cycls"}
39
+ files.update({f: f for f in copy or []})
40
+ files.update({f: f"public/{f}" for f in self.copy_public})
41
+
42
+ super().__init__(
43
+ func=func,
44
+ name=name,
45
+ pip=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", "python-dotenv", "docker", "agentfs-sdk", "pyturso==0.4.0rc17", *(pip or [])],
46
+ apt=apt,
47
+ copy=files,
48
+ base_url=_get_base_url(),
49
+ api_key=_get_api_key()
50
+ )
51
+
52
+ def __call__(self, *args, **kwargs):
53
+ return self.user_func(*args, **kwargs)
54
+
55
+ def _prepare_func(self, prod):
56
+ self.config.set_prod(prod)
57
+ self.config.public_path = f"cycls/themes/{self.theme}"
58
+ user_func, config, name = self.user_func, self.config, self.name
59
+ self.func = lambda port: __import__("cycls").web.serve(user_func, config, name, port)
60
+
61
+ def _local(self, port=8080):
62
+ """Run directly with uvicorn (no Docker)."""
63
+ print(f"Starting local server at localhost:{port}")
64
+ self.config.public_path = str(CYCLS_PATH.joinpath(f"themes/{self.theme}"))
65
+ self.config.set_prod(False)
66
+ uvicorn.run(web(self.user_func, self.config), host="0.0.0.0", port=port)
67
+
68
+ def local(self, port=8080, watch=True):
69
+ """Run locally in Docker with file watching by default."""
70
+ if os.environ.get('_CYCLS_WATCH'):
71
+ watch = False
72
+ self._prepare_func(prod=False)
73
+ self.watch(port=port) if watch else self.run(port=port)
74
+
75
+ def deploy(self, port=8080):
76
+ """Deploy to production."""
77
+ if self.api_key is None:
78
+ raise RuntimeError("Missing API key. Set cycls.api_key or CYCLS_API_KEY environment variable.")
79
+ self._prepare_func(prod=True)
80
+ return super().deploy(port=port)
81
+
82
+
83
+ def app(name=None, **kwargs):
84
+ """Decorator that transforms a function into a deployable App."""
85
+ if kwargs.get("plan") == "cycls_pass":
86
+ kwargs["auth"] = True
87
+ kwargs["analytics"] = True
88
+
89
+ def decorator(func):
90
+ return App(func=func, name=name or func.__name__, **kwargs)
91
+ return decorator
@@ -0,0 +1,4 @@
1
+ JWKS_PROD = "https://clerk.cycls.ai/.well-known/jwks.json"
2
+ JWKS_TEST = "https://select-sloth-58.clerk.accounts.dev/.well-known/jwks.json"
3
+ PK_LIVE = "pk_live_Y2xlcmsuY3ljbHMuYWkk"
4
+ PK_TEST = "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"