cycls 0.0.2.23__tar.gz → 0.0.2.30__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.
- cycls-0.0.2.30/PKG-INFO +103 -0
- cycls-0.0.2.30/README.md +83 -0
- cycls-0.0.2.30/cycls/__init__.py +1 -0
- cycls-0.0.2.30/cycls/cycls.py +162 -0
- cycls-0.0.2.30/cycls/theme/assets/index-D0-uI8sw.js +364 -0
- cycls-0.0.2.30/cycls/theme/index.html +23 -0
- {cycls-0.0.2.23 → cycls-0.0.2.30}/pyproject.toml +6 -4
- cycls-0.0.2.23/PKG-INFO +0 -94
- cycls-0.0.2.23/README.md +0 -75
- cycls-0.0.2.23/cycls/__init__.py +0 -1
- cycls-0.0.2.23/cycls/cycls.py +0 -124
- cycls-0.0.2.23/cycls/tuns +0 -27
cycls-0.0.2.30/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cycls
|
|
3
|
+
Version: 0.0.2.30
|
|
4
|
+
Summary: Cycls SDK
|
|
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
|
+
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
15
|
+
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
16
|
+
Requires-Dist: jwt (>=1.4.0,<2.0.0)
|
|
17
|
+
Requires-Dist: modal (>=1.1.0,<2.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="https://github.com/user-attachments/assets/96bd304d-8116-4bce-8b8f-b08980875ad7" width="800px" alt="Cycls Banner">
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<h3 align="center">
|
|
25
|
+
Generate live apps from code in minutes with built-in memory, <br/>rich hypermedia content, and cross-platform support
|
|
26
|
+
</h3>
|
|
27
|
+
|
|
28
|
+
<h4 align="center">
|
|
29
|
+
<a href="https://cycls.com">Website</a> |
|
|
30
|
+
<a href="https://docs.cycls.com">Docs</a> |
|
|
31
|
+
<a href="https://docs.cycls.com">Blog</a>
|
|
32
|
+
</h4>
|
|
33
|
+
|
|
34
|
+
<h4 align="center">
|
|
35
|
+
<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>
|
|
36
|
+
<a href="https://blog.cycls.com"><img src="https://img.shields.io/badge/newsletter-blueviolet.svg?logo=substack&label=cycls" alt="Cycls newsletter" /></a>
|
|
37
|
+
<a href="https://x.com/cycls_">
|
|
38
|
+
<img src="https://img.shields.io/twitter/follow/cycls_" alt="Cycls Twitter" />
|
|
39
|
+
</a>
|
|
40
|
+
</h4>
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Cycls: The AI App Generator
|
|
44
|
+
Cycls[^1] streamlines AI application development by generating apps from high-level descriptions. It eliminates boilerplate, ensures cross-platform compatibility, and manages memory - all from a single codebase.
|
|
45
|
+
|
|
46
|
+
With Cycls, you can quickly prototype ideas and then turn them into production apps, while focusing on AI logic and user interactions rather than wrestling with implementation details.
|
|
47
|
+
|
|
48
|
+
## ✨ Core Features
|
|
49
|
+
- **Fast App Generation**: Create live web apps from code in minutes
|
|
50
|
+
- **Built-in Memory Management**: Integrated state and session management
|
|
51
|
+
- **Rich Hypermedia Content**: Support for various media types (text, images, audio, video, interactive elements)
|
|
52
|
+
- **Framework Agnostic**: Compatible with a wide range of AI frameworks and models
|
|
53
|
+
|
|
54
|
+
## 🚀 Quickstart
|
|
55
|
+
### Installation
|
|
56
|
+
```
|
|
57
|
+
pip install cycls
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Basic usage
|
|
61
|
+
```py
|
|
62
|
+
from cycls import Cycls
|
|
63
|
+
|
|
64
|
+
cycls = Cycls()
|
|
65
|
+
|
|
66
|
+
@cycls("@my-app")
|
|
67
|
+
def app():
|
|
68
|
+
return "Hello World!"
|
|
69
|
+
|
|
70
|
+
cycls.push()
|
|
71
|
+
```
|
|
72
|
+
This creates an app named "@my-app" that responds with "Hello World!".
|
|
73
|
+
|
|
74
|
+
The `@cycls("@my-app")` decorator registers your app, and `cycls.push()` streams it to Cycls platform.
|
|
75
|
+
|
|
76
|
+
To see a live example, visit https://cycls.com/@spark.
|
|
77
|
+
|
|
78
|
+
> [!IMPORTANT]
|
|
79
|
+
> Use a unique name for your app (like "@my-app"). This is your app's identifier on Cycls.
|
|
80
|
+
|
|
81
|
+
> [!NOTE]
|
|
82
|
+
> Your apps run on your infrastructure and are streamed in real-time to Cycls.
|
|
83
|
+
|
|
84
|
+
## 📖 Documentation
|
|
85
|
+
For more detailes and instructions, visit our documentation at [docs.cycls.com](https://docs.cycls.com/).
|
|
86
|
+
|
|
87
|
+
## 🗺️ Roadmap
|
|
88
|
+
- **iOS and Android apps**
|
|
89
|
+
- **User management**
|
|
90
|
+
- **JavaScript SDK**
|
|
91
|
+
- **Public API**
|
|
92
|
+
- **Cross-app communication**
|
|
93
|
+
|
|
94
|
+
## 🙌 Support
|
|
95
|
+
Join our Discord community for support and discussions. You can reach us on:
|
|
96
|
+
|
|
97
|
+
- [Join our Discord](https://discord.gg/XbxcTFBf7J)
|
|
98
|
+
- [Join our newsletter](https://blog.cycls.com)
|
|
99
|
+
- [Follow us on Twitter](https://x.com/cycls_)
|
|
100
|
+
- [Email us](mailto:hi@cycls.com)
|
|
101
|
+
|
|
102
|
+
[^1]: The name "Cycls" is a play on "cycles," referring to the continuous exchange between AI prompts (generators) and their responses (generated).
|
|
103
|
+
|
cycls-0.0.2.30/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://github.com/user-attachments/assets/96bd304d-8116-4bce-8b8f-b08980875ad7" width="800px" alt="Cycls Banner">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h3 align="center">
|
|
6
|
+
Generate live apps from code in minutes with built-in memory, <br/>rich hypermedia content, and cross-platform support
|
|
7
|
+
</h3>
|
|
8
|
+
|
|
9
|
+
<h4 align="center">
|
|
10
|
+
<a href="https://cycls.com">Website</a> |
|
|
11
|
+
<a href="https://docs.cycls.com">Docs</a> |
|
|
12
|
+
<a href="https://docs.cycls.com">Blog</a>
|
|
13
|
+
</h4>
|
|
14
|
+
|
|
15
|
+
<h4 align="center">
|
|
16
|
+
<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>
|
|
17
|
+
<a href="https://blog.cycls.com"><img src="https://img.shields.io/badge/newsletter-blueviolet.svg?logo=substack&label=cycls" alt="Cycls newsletter" /></a>
|
|
18
|
+
<a href="https://x.com/cycls_">
|
|
19
|
+
<img src="https://img.shields.io/twitter/follow/cycls_" alt="Cycls Twitter" />
|
|
20
|
+
</a>
|
|
21
|
+
</h4>
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Cycls: The AI App Generator
|
|
25
|
+
Cycls[^1] streamlines AI application development by generating apps from high-level descriptions. It eliminates boilerplate, ensures cross-platform compatibility, and manages memory - all from a single codebase.
|
|
26
|
+
|
|
27
|
+
With Cycls, you can quickly prototype ideas and then turn them into production apps, while focusing on AI logic and user interactions rather than wrestling with implementation details.
|
|
28
|
+
|
|
29
|
+
## ✨ Core Features
|
|
30
|
+
- **Fast App Generation**: Create live web apps from code in minutes
|
|
31
|
+
- **Built-in Memory Management**: Integrated state and session management
|
|
32
|
+
- **Rich Hypermedia Content**: Support for various media types (text, images, audio, video, interactive elements)
|
|
33
|
+
- **Framework Agnostic**: Compatible with a wide range of AI frameworks and models
|
|
34
|
+
|
|
35
|
+
## 🚀 Quickstart
|
|
36
|
+
### Installation
|
|
37
|
+
```
|
|
38
|
+
pip install cycls
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Basic usage
|
|
42
|
+
```py
|
|
43
|
+
from cycls import Cycls
|
|
44
|
+
|
|
45
|
+
cycls = Cycls()
|
|
46
|
+
|
|
47
|
+
@cycls("@my-app")
|
|
48
|
+
def app():
|
|
49
|
+
return "Hello World!"
|
|
50
|
+
|
|
51
|
+
cycls.push()
|
|
52
|
+
```
|
|
53
|
+
This creates an app named "@my-app" that responds with "Hello World!".
|
|
54
|
+
|
|
55
|
+
The `@cycls("@my-app")` decorator registers your app, and `cycls.push()` streams it to Cycls platform.
|
|
56
|
+
|
|
57
|
+
To see a live example, visit https://cycls.com/@spark.
|
|
58
|
+
|
|
59
|
+
> [!IMPORTANT]
|
|
60
|
+
> Use a unique name for your app (like "@my-app"). This is your app's identifier on Cycls.
|
|
61
|
+
|
|
62
|
+
> [!NOTE]
|
|
63
|
+
> Your apps run on your infrastructure and are streamed in real-time to Cycls.
|
|
64
|
+
|
|
65
|
+
## 📖 Documentation
|
|
66
|
+
For more detailes and instructions, visit our documentation at [docs.cycls.com](https://docs.cycls.com/).
|
|
67
|
+
|
|
68
|
+
## 🗺️ Roadmap
|
|
69
|
+
- **iOS and Android apps**
|
|
70
|
+
- **User management**
|
|
71
|
+
- **JavaScript SDK**
|
|
72
|
+
- **Public API**
|
|
73
|
+
- **Cross-app communication**
|
|
74
|
+
|
|
75
|
+
## 🙌 Support
|
|
76
|
+
Join our Discord community for support and discussions. You can reach us on:
|
|
77
|
+
|
|
78
|
+
- [Join our Discord](https://discord.gg/XbxcTFBf7J)
|
|
79
|
+
- [Join our newsletter](https://blog.cycls.com)
|
|
80
|
+
- [Follow us on Twitter](https://x.com/cycls_)
|
|
81
|
+
- [Email us](mailto:hi@cycls.com)
|
|
82
|
+
|
|
83
|
+
[^1]: The name "Cycls" is a play on "cycles," referring to the continuous exchange between AI prompts (generators) and their responses (generated).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cycls import Agent
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import json, time, modal, inspect, uvicorn
|
|
2
|
+
from modal.runner import run_app
|
|
3
|
+
|
|
4
|
+
import importlib.resources
|
|
5
|
+
theme_path = importlib.resources.files('cycls').joinpath('theme')
|
|
6
|
+
|
|
7
|
+
async def openai_encoder(stream): # clean up the meta data / new API?
|
|
8
|
+
async for message in stream:
|
|
9
|
+
payload = {"id": "chatcmpl-123",
|
|
10
|
+
"object": "chat.completion.chunk",
|
|
11
|
+
"created": 1728083325,
|
|
12
|
+
"model": "model-1-2025-01-01",
|
|
13
|
+
"system_fingerprint": "fp_123456",
|
|
14
|
+
"choices": [{"delta": {"content": message}}]}
|
|
15
|
+
if message:
|
|
16
|
+
yield f"data: {json.dumps(payload)}\n\n"
|
|
17
|
+
yield "data: [DONE]\n\n"
|
|
18
|
+
|
|
19
|
+
test_auth_public_key = """
|
|
20
|
+
-----BEGIN PUBLIC KEY-----
|
|
21
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyDudrDtQ5irw6hPWf2rw
|
|
22
|
+
FvNAFWeOouOO3XNWVQrjXCZfegiLYkL4cJdm4eqIuMdFHGnXU+gWT5P0EkLIkbtE
|
|
23
|
+
zpqDb5Wp27WpSRb5lqJehpU7FE+oQuovCwR9m5gYXP5rfM+CQ7ZPw/CcOQPtOB5G
|
|
24
|
+
0UijBhmYqws3SFp1Rk1uFed1F/esspt6Ifq2uDSHESleylqTKUCQiBa++z4wllcV
|
|
25
|
+
PbNiooLRpsF0kGljP2dXXy/ViF7q9Cblgl+FdrqtGfHD+DHJuOSYcPnRa0IHZYS4
|
|
26
|
+
r5i9C2lejVrEDqgJk5IbmQgez0wmEG4ynAxiDLvfdtvrd27PyBI75FsyLER/ydBH
|
|
27
|
+
WwIDAQAB
|
|
28
|
+
-----END PUBLIC KEY-----
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
live_auth_public_key = """
|
|
32
|
+
-----BEGIN PUBLIC KEY-----
|
|
33
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAorfL7XyxrLG/X+Kq9ImY
|
|
34
|
+
oSQ+Y3PY5qi8t8R4urY9u4ADJ48j9LkmFz8ALbubQkl3IByDDuVbka49m8id9isy
|
|
35
|
+
F9ZJErsZzzlYztrgI5Sg4R6OJXcNWLqh/tzutMWJFOrE3LnHXpeyQMo/6qAd59Dx
|
|
36
|
+
sNqzGxBTGPV1BZvpfhp/TT/sjgbPQWHS4PMpKD4vZLKXeTNJ913fMTUoFAIaL0sT
|
|
37
|
+
EhoeLUwvIuhLx4UYTmjO/sa+fS6mdghjddOkjSS/AWr/K8mN3IXDImGqh83L7/P0
|
|
38
|
+
RCru4Hvarm0qPIhfwEFfWhKFXONMj3x2fT4MM1Uw1H7qKTER2MtOjmdchKNX7x9b
|
|
39
|
+
XwIDAQAB
|
|
40
|
+
-----END PUBLIC KEY-----
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def web(func, front_end_path="", prod=False, org=None, api_token=None, header="", intro="", auth=True): # API auth
|
|
44
|
+
print(front_end_path)
|
|
45
|
+
from fastapi import FastAPI, Request, HTTPException, status, Depends
|
|
46
|
+
from fastapi.responses import StreamingResponse , HTMLResponse
|
|
47
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
48
|
+
import jwt
|
|
49
|
+
from pydantic import BaseModel, EmailStr
|
|
50
|
+
from typing import List, Optional
|
|
51
|
+
from fastapi.templating import Jinja2Templates
|
|
52
|
+
from fastapi.staticfiles import StaticFiles
|
|
53
|
+
|
|
54
|
+
class User(BaseModel):
|
|
55
|
+
id: str
|
|
56
|
+
name: str
|
|
57
|
+
email: EmailStr
|
|
58
|
+
org: Optional[str] = None
|
|
59
|
+
plans: List[str] = []
|
|
60
|
+
|
|
61
|
+
class Context(BaseModel):
|
|
62
|
+
messages: List[dict]
|
|
63
|
+
user: Optional[User] = None
|
|
64
|
+
|
|
65
|
+
app = FastAPI()
|
|
66
|
+
bearer_scheme = HTTPBearer()
|
|
67
|
+
|
|
68
|
+
def validate(bearer: HTTPAuthorizationCredentials = Depends(bearer_scheme)):
|
|
69
|
+
# if api_token and api_token==""
|
|
70
|
+
try:
|
|
71
|
+
public_key = live_auth_public_key if prod else test_auth_public_key
|
|
72
|
+
decoded = jwt.decode(bearer.credentials, public_key, algorithms=["RS256"])
|
|
73
|
+
# print(decoded)
|
|
74
|
+
return {"type": "user",
|
|
75
|
+
"user": {"id": decoded.get("id"), "name": decoded.get("name"), "email": decoded.get("email"), "org": decoded.get("org"),
|
|
76
|
+
"plans": decoded.get("public").get("plans", [])}}
|
|
77
|
+
except:
|
|
78
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials", headers={"WWW-Authenticate": "Bearer"})
|
|
79
|
+
|
|
80
|
+
@app.post("/")
|
|
81
|
+
@app.post("/chat/completions")
|
|
82
|
+
async def back(request: Request, jwt: Optional[dict] = Depends(validate) if auth else None):
|
|
83
|
+
data = await request.json()
|
|
84
|
+
messages = data.get("messages")
|
|
85
|
+
user_data = jwt.get("user") if jwt else None
|
|
86
|
+
context = Context(messages = messages, user = User(**user_data) if user_data else None)
|
|
87
|
+
stream = await func(context) if inspect.iscoroutinefunction(func) else func(context)
|
|
88
|
+
if request.url.path == "/chat/completions":
|
|
89
|
+
stream = openai_encoder(stream)
|
|
90
|
+
return StreamingResponse(stream, media_type="text/event-stream")
|
|
91
|
+
|
|
92
|
+
templates = Jinja2Templates(directory=front_end_path)
|
|
93
|
+
@app.get("/", response_class=HTMLResponse)
|
|
94
|
+
async def front(request: Request):
|
|
95
|
+
return templates.TemplateResponse("index.html", {
|
|
96
|
+
"request": request, "header": header, "intro": intro, "prod": prod, "auth": auth, "org": org,
|
|
97
|
+
"pk_live": "pk_live_Y2xlcmsuY3ljbHMuY29tJA", "pk_test": "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"
|
|
98
|
+
})
|
|
99
|
+
app.mount("/", StaticFiles(directory=front_end_path, html=False))
|
|
100
|
+
return app
|
|
101
|
+
|
|
102
|
+
class Agent:
|
|
103
|
+
def __init__(self, front_end=theme_path, organization=None, api_token=None, pip=[], apt=[], copy=[], production=False, keys=["",""]):
|
|
104
|
+
self.prod, self.org, self.api_token = production, organization, api_token
|
|
105
|
+
self.front_end = front_end
|
|
106
|
+
self.registered_functions = []
|
|
107
|
+
self.client = modal.Client.from_credentials(*keys)
|
|
108
|
+
image = (modal.Image.debian_slim()
|
|
109
|
+
.pip_install("fastapi[standard]", "pyjwt", "cryptography", *pip)
|
|
110
|
+
.apt_install(*apt)
|
|
111
|
+
.add_local_dir(front_end, "/root/public")
|
|
112
|
+
.add_local_python_source("cycls"))
|
|
113
|
+
for item in copy:
|
|
114
|
+
image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
|
|
115
|
+
self.app = modal.App("development", image=image)
|
|
116
|
+
|
|
117
|
+
def __call__(self, name="", header="", intro="", domain=None, auth=False):
|
|
118
|
+
def decorator(f):
|
|
119
|
+
self.registered_functions.append({
|
|
120
|
+
"func": f,
|
|
121
|
+
"config": ["public", self.prod, self.org, self.api_token, header, intro, auth],
|
|
122
|
+
"name": name,
|
|
123
|
+
"domain": domain or f"{name}.cycls.ai",
|
|
124
|
+
})
|
|
125
|
+
return f
|
|
126
|
+
return decorator
|
|
127
|
+
|
|
128
|
+
def run(self, port=8000):
|
|
129
|
+
if not self.registered_functions:
|
|
130
|
+
return print("Error: No @agent decorated function found.")
|
|
131
|
+
|
|
132
|
+
i = self.registered_functions[0]
|
|
133
|
+
if len(self.registered_functions) > 1:
|
|
134
|
+
print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
|
|
135
|
+
print(f"🚀 Starting local server at http://127.0.0.1:{port}")
|
|
136
|
+
i["config"][0] = self.front_end
|
|
137
|
+
uvicorn.run(web(i["func"], *i["config"]), host="127.0.0.1", port=port)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
def push(self): # local / prod?
|
|
141
|
+
if not self.registered_functions:
|
|
142
|
+
return print("Error: No @agent decorated function found.")
|
|
143
|
+
|
|
144
|
+
for i in self.registered_functions:
|
|
145
|
+
self.app.function(serialized=True, name=i["name"])(
|
|
146
|
+
modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
|
|
147
|
+
(lambda: web(i["func"], *i["config"]))
|
|
148
|
+
)
|
|
149
|
+
if self.prod:
|
|
150
|
+
for i in self.registered_functions:
|
|
151
|
+
print(f"✅ Deployed to ⇒ https://{i['domain']}")
|
|
152
|
+
self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
|
|
153
|
+
return
|
|
154
|
+
else:
|
|
155
|
+
with modal.enable_output():
|
|
156
|
+
run_app(app=self.app, client=self.client)
|
|
157
|
+
print(" Modal development server is running. Press Ctrl+C to stop.")
|
|
158
|
+
with modal.enable_output(), run_app(app=self.app, client=self.client):
|
|
159
|
+
while True: time.sleep(10)
|
|
160
|
+
|
|
161
|
+
# poetry run python agent.py
|
|
162
|
+
# poetry publish --build
|