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