cycls 0.0.2.82__tar.gz → 0.0.2.84__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycls
3
- Version: 0.0.2.82
3
+ Version: 0.0.2.84
4
4
  Summary: Distribute Intelligence
5
5
  Author: Mohammed J. AlRujayi
6
6
  Author-email: mj@cycls.com
@@ -12,14 +12,10 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
- Provides-Extra: modal
16
15
  Requires-Dist: cloudpickle (>=3.1.1,<4.0.0)
17
16
  Requires-Dist: docker (>=7.1.0,<8.0.0)
18
17
  Requires-Dist: fastapi (>=0.111.0,<0.112.0)
19
- Requires-Dist: grpcio (>=1.76.0,<2.0.0)
20
18
  Requires-Dist: httpx (>=0.27.0,<0.28.0)
21
- Requires-Dist: modal (>=1.1.0,<2.0.0) ; extra == "modal"
22
- Requires-Dist: protobuf (>=6.0,<7.0)
23
19
  Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
24
20
  Description-Content-Type: text/markdown
25
21
 
@@ -49,6 +45,8 @@ The open-source SDK for distributing AI agents.
49
45
 
50
46
  The function is the unit of abstraction in cycls. Your agent logic lives in a plain Python function — the decorator layers on everything else: containerization, authentication, deployment, analytics. You write the function, the `@` handles the infrastructure.
51
47
 
48
+ Cycls is beautifully lean (~862 lines!) and focused on deployment infrastructure for any Python function, not agent logic itself.
49
+
52
50
  ## Distribute Intelligence
53
51
 
54
52
  Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
@@ -58,8 +56,8 @@ import cycls
58
56
 
59
57
  cycls.api_key = "YOUR_CYCLS_API_KEY"
60
58
 
61
- @cycls.agent(pip=["openai"])
62
- async def agent(context):
59
+ @cycls.app(pip=["openai"])
60
+ async def app(context):
63
61
  from openai import AsyncOpenAI
64
62
  client = AsyncOpenAI()
65
63
 
@@ -76,7 +74,7 @@ async def agent(context):
76
74
  elif event.type == "response.output_text.delta":
77
75
  yield event.delta
78
76
 
79
- agent.deploy() # Live at https://agent.cycls.ai
77
+ app.deploy() # Live at https://agent.cycls.ai
80
78
  ```
81
79
 
82
80
  ## Installation
@@ -99,9 +97,9 @@ Requires Docker.
99
97
  ## Running
100
98
 
101
99
  ```python
102
- agent.local() # Development with hot-reload (localhost:8080)
103
- agent.local(watch=False) # Development without hot-reload
104
- agent.deploy() # Production: https://agent.cycls.ai
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
105
103
  ```
106
104
 
107
105
  Get an API key at [cycls.com](https://cycls.com).
@@ -109,8 +107,8 @@ Get an API key at [cycls.com](https://cycls.com).
109
107
  ## Authentication & Analytics
110
108
 
111
109
  ```python
112
- @cycls.agent(pip=["openai"], auth=True, analytics=True)
113
- async def agent(context):
110
+ @cycls.app(pip=["openai"], auth=True, analytics=True)
111
+ async def app(context):
114
112
  # context.user available when auth=True
115
113
  user = context.user # User(id, email, name, plans)
116
114
  yield f"Hello {user.name}!"
@@ -127,7 +125,7 @@ async def agent(context):
127
125
  Yield structured objects for rich streaming responses:
128
126
 
129
127
  ```python
130
- @cycls.agent()
128
+ @cycls.app()
131
129
  async def demo(context):
132
130
  yield {"type": "thinking", "thinking": "Analyzing the request..."}
133
131
  yield "Here's what I found:\n\n"
@@ -169,7 +167,7 @@ This works seamlessly with OpenAI's reasoning models - just map reasoning summar
169
167
  ## Context Object
170
168
 
171
169
  ```python
172
- @cycls.agent()
170
+ @cycls.app()
173
171
  async def chat(context):
174
172
  context.messages # [{"role": "user", "content": "..."}]
175
173
  context.messages.raw # Full data including UI component parts
@@ -202,13 +200,13 @@ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integr
202
200
  Define your entire runtime in the decorator:
203
201
 
204
202
  ```python
205
- @cycls.agent(
203
+ @cycls.app(
206
204
  pip=["openai", "pandas", "numpy"],
207
205
  apt=["ffmpeg", "libmagic1"],
208
206
  copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
209
207
  copy_public=["./assets/logo.png", "./static/"],
210
208
  )
211
- async def my_agent(context):
209
+ async def my_app(context):
212
210
  ...
213
211
  ```
214
212
 
@@ -243,7 +241,7 @@ copy=[
243
241
  Then import them in your function:
244
242
 
245
243
  ```python
246
- @cycls.agent(copy=["./utils.py"])
244
+ @cycls.app(copy=["./utils.py"])
247
245
  async def chat(context):
248
246
  from utils import helper_function # Your bundled module
249
247
  ...
@@ -257,7 +255,7 @@ Files and directories served at the `/public` endpoint. Perfect for images, down
257
255
  copy_public=["./assets/logo.png", "./downloads/"]
258
256
  ```
259
257
 
260
- Access them at `https://your-agent.cycls.ai/public/logo.png`.
258
+ Access them at `https://your-app.cycls.ai/public/logo.png`.
261
259
 
262
260
  ---
263
261
 
@@ -24,6 +24,8 @@ The open-source SDK for distributing AI agents.
24
24
 
25
25
  The function is the unit of abstraction in cycls. Your agent logic lives in a plain Python function — the decorator layers on everything else: containerization, authentication, deployment, analytics. You write the function, the `@` handles the infrastructure.
26
26
 
27
+ Cycls is beautifully lean (~862 lines!) and focused on deployment infrastructure for any Python function, not agent logic itself.
28
+
27
29
  ## Distribute Intelligence
28
30
 
29
31
  Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
@@ -33,8 +35,8 @@ import cycls
33
35
 
34
36
  cycls.api_key = "YOUR_CYCLS_API_KEY"
35
37
 
36
- @cycls.agent(pip=["openai"])
37
- async def agent(context):
38
+ @cycls.app(pip=["openai"])
39
+ async def app(context):
38
40
  from openai import AsyncOpenAI
39
41
  client = AsyncOpenAI()
40
42
 
@@ -51,7 +53,7 @@ async def agent(context):
51
53
  elif event.type == "response.output_text.delta":
52
54
  yield event.delta
53
55
 
54
- agent.deploy() # Live at https://agent.cycls.ai
56
+ app.deploy() # Live at https://agent.cycls.ai
55
57
  ```
56
58
 
57
59
  ## Installation
@@ -74,9 +76,9 @@ Requires Docker.
74
76
  ## Running
75
77
 
76
78
  ```python
77
- agent.local() # Development with hot-reload (localhost:8080)
78
- agent.local(watch=False) # Development without hot-reload
79
- agent.deploy() # Production: https://agent.cycls.ai
79
+ app.local() # Development with hot-reload (localhost:8080)
80
+ app.local(watch=False) # Development without hot-reload
81
+ app.deploy() # Production: https://agent.cycls.ai
80
82
  ```
81
83
 
82
84
  Get an API key at [cycls.com](https://cycls.com).
@@ -84,8 +86,8 @@ Get an API key at [cycls.com](https://cycls.com).
84
86
  ## Authentication & Analytics
85
87
 
86
88
  ```python
87
- @cycls.agent(pip=["openai"], auth=True, analytics=True)
88
- async def agent(context):
89
+ @cycls.app(pip=["openai"], auth=True, analytics=True)
90
+ async def app(context):
89
91
  # context.user available when auth=True
90
92
  user = context.user # User(id, email, name, plans)
91
93
  yield f"Hello {user.name}!"
@@ -102,7 +104,7 @@ async def agent(context):
102
104
  Yield structured objects for rich streaming responses:
103
105
 
104
106
  ```python
105
- @cycls.agent()
107
+ @cycls.app()
106
108
  async def demo(context):
107
109
  yield {"type": "thinking", "thinking": "Analyzing the request..."}
108
110
  yield "Here's what I found:\n\n"
@@ -144,7 +146,7 @@ This works seamlessly with OpenAI's reasoning models - just map reasoning summar
144
146
  ## Context Object
145
147
 
146
148
  ```python
147
- @cycls.agent()
149
+ @cycls.app()
148
150
  async def chat(context):
149
151
  context.messages # [{"role": "user", "content": "..."}]
150
152
  context.messages.raw # Full data including UI component parts
@@ -177,13 +179,13 @@ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integr
177
179
  Define your entire runtime in the decorator:
178
180
 
179
181
  ```python
180
- @cycls.agent(
182
+ @cycls.app(
181
183
  pip=["openai", "pandas", "numpy"],
182
184
  apt=["ffmpeg", "libmagic1"],
183
185
  copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
184
186
  copy_public=["./assets/logo.png", "./static/"],
185
187
  )
186
- async def my_agent(context):
188
+ async def my_app(context):
187
189
  ...
188
190
  ```
189
191
 
@@ -218,7 +220,7 @@ copy=[
218
220
  Then import them in your function:
219
221
 
220
222
  ```python
221
- @cycls.agent(copy=["./utils.py"])
223
+ @cycls.app(copy=["./utils.py"])
222
224
  async def chat(context):
223
225
  from utils import helper_function # Your bundled module
224
226
  ...
@@ -232,7 +234,7 @@ Files and directories served at the `/public` endpoint. Perfect for images, down
232
234
  copy_public=["./assets/logo.png", "./downloads/"]
233
235
  ```
234
236
 
235
- Access them at `https://your-agent.cycls.ai/public/logo.png`.
237
+ Access them at `https://your-app.cycls.ai/public/logo.png`.
236
238
 
237
239
  ---
238
240
 
@@ -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", "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