cycls 0.0.2.69__py3-none-any.whl → 0.0.2.71__py3-none-any.whl
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/cli.py +217 -0
- cycls/runtime.py +29 -66
- cycls/sdk.py +26 -19
- {cycls-0.0.2.69.dist-info → cycls-0.0.2.71.dist-info}/METADATA +6 -5
- {cycls-0.0.2.69.dist-info → cycls-0.0.2.71.dist-info}/RECORD +7 -5
- cycls-0.0.2.71.dist-info/entry_points.txt +3 -0
- {cycls-0.0.2.69.dist-info → cycls-0.0.2.71.dist-info}/WHEEL +0 -0
cycls/cli.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import threading
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
# ANSI codes
|
|
8
|
+
DIM = "\033[2m"
|
|
9
|
+
BOLD = "\033[1m"
|
|
10
|
+
RESET = "\033[0m"
|
|
11
|
+
CLEAR_LINE = "\r\033[K"
|
|
12
|
+
GREEN = "\033[32m"
|
|
13
|
+
YELLOW = "\033[33m"
|
|
14
|
+
BLUE = "\033[34m"
|
|
15
|
+
RED = "\033[31m"
|
|
16
|
+
CYAN = "\033[36m"
|
|
17
|
+
MAGENTA = "\033[35m"
|
|
18
|
+
|
|
19
|
+
CALLOUT_STYLES = {
|
|
20
|
+
"success": ("✓", GREEN),
|
|
21
|
+
"warning": ("⚠", YELLOW),
|
|
22
|
+
"info": ("ℹ", BLUE),
|
|
23
|
+
"error": ("✗", RED),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def format_time(seconds):
|
|
27
|
+
if seconds < 60:
|
|
28
|
+
return f"{seconds:.1f}s"
|
|
29
|
+
return f"{int(seconds // 60)}m {int(seconds % 60)}s"
|
|
30
|
+
|
|
31
|
+
def render_table(headers, rows):
|
|
32
|
+
if not headers:
|
|
33
|
+
return
|
|
34
|
+
widths = [len(str(h)) for h in headers]
|
|
35
|
+
for row in rows:
|
|
36
|
+
for i, cell in enumerate(row):
|
|
37
|
+
if i < len(widths):
|
|
38
|
+
widths[i] = max(widths[i], len(str(cell)))
|
|
39
|
+
|
|
40
|
+
top = "┌" + "┬".join("─" * (w + 2) for w in widths) + "┐"
|
|
41
|
+
sep = "├" + "┼".join("─" * (w + 2) for w in widths) + "┤"
|
|
42
|
+
bot = "└" + "┴".join("─" * (w + 2) for w in widths) + "┘"
|
|
43
|
+
|
|
44
|
+
def fmt_row(cells, bold=False):
|
|
45
|
+
parts = []
|
|
46
|
+
for i, w in enumerate(widths):
|
|
47
|
+
cell = str(cells[i]) if i < len(cells) else ""
|
|
48
|
+
if bold:
|
|
49
|
+
parts.append(f" {BOLD}{cell.ljust(w)}{RESET} ")
|
|
50
|
+
else:
|
|
51
|
+
parts.append(f" {cell.ljust(w)} ")
|
|
52
|
+
return "│" + "│".join(parts) + "│"
|
|
53
|
+
|
|
54
|
+
print(top)
|
|
55
|
+
print(fmt_row(headers, bold=True))
|
|
56
|
+
print(sep)
|
|
57
|
+
for row in rows:
|
|
58
|
+
print(fmt_row(row))
|
|
59
|
+
print(bot)
|
|
60
|
+
|
|
61
|
+
class Spinner:
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self.active = False
|
|
64
|
+
self.thread = None
|
|
65
|
+
self.frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
66
|
+
|
|
67
|
+
def start(self):
|
|
68
|
+
self.active = True
|
|
69
|
+
def spin():
|
|
70
|
+
i = 0
|
|
71
|
+
while self.active:
|
|
72
|
+
if not self.active:
|
|
73
|
+
break
|
|
74
|
+
print(f"\r{MAGENTA}{self.frames[i % len(self.frames)]}{RESET} ", end="", flush=True)
|
|
75
|
+
for _ in range(8): # Check active more frequently
|
|
76
|
+
if not self.active:
|
|
77
|
+
break
|
|
78
|
+
time.sleep(0.01)
|
|
79
|
+
i += 1
|
|
80
|
+
self.thread = threading.Thread(target=spin, daemon=True)
|
|
81
|
+
self.thread.start()
|
|
82
|
+
|
|
83
|
+
def stop(self):
|
|
84
|
+
self.active = False
|
|
85
|
+
if self.thread:
|
|
86
|
+
self.thread.join(timeout=0.2)
|
|
87
|
+
print(f"{CLEAR_LINE}", end="", flush=True)
|
|
88
|
+
|
|
89
|
+
def chat(url):
|
|
90
|
+
messages = []
|
|
91
|
+
endpoint = f"{url.rstrip('/')}/chat/cycls"
|
|
92
|
+
|
|
93
|
+
print(f"\n{MAGENTA}●{RESET} {BOLD}{url}{RESET}\n")
|
|
94
|
+
|
|
95
|
+
while True:
|
|
96
|
+
try:
|
|
97
|
+
user_input = input(f"{CYAN}❯{RESET} ")
|
|
98
|
+
if not user_input.strip():
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
messages.append({"role": "user", "content": user_input})
|
|
102
|
+
print()
|
|
103
|
+
|
|
104
|
+
start_time = time.time()
|
|
105
|
+
in_thinking = False
|
|
106
|
+
table_headers = []
|
|
107
|
+
table_rows = []
|
|
108
|
+
|
|
109
|
+
with httpx.stream("POST", endpoint, json={"messages": messages}, timeout=None) as r:
|
|
110
|
+
for line in r.iter_lines():
|
|
111
|
+
if line.startswith("data: ") and line != "data: [DONE]":
|
|
112
|
+
|
|
113
|
+
data = json.loads(line[6:])
|
|
114
|
+
msg_type = data.get("type")
|
|
115
|
+
|
|
116
|
+
# Handle plain string or missing type
|
|
117
|
+
if msg_type is None:
|
|
118
|
+
# Could be OpenAI format or plain text
|
|
119
|
+
if isinstance(data, str):
|
|
120
|
+
print(data, end="", flush=True)
|
|
121
|
+
continue
|
|
122
|
+
elif "choices" in data:
|
|
123
|
+
# OpenAI format
|
|
124
|
+
content = data.get("choices", [{}])[0].get("delta", {}).get("content", "")
|
|
125
|
+
if content:
|
|
126
|
+
print(content, end="", flush=True)
|
|
127
|
+
continue
|
|
128
|
+
elif "text" in data:
|
|
129
|
+
print(data.get("text", ""), end="", flush=True)
|
|
130
|
+
continue
|
|
131
|
+
elif "content" in data:
|
|
132
|
+
print(data.get("content", ""), end="", flush=True)
|
|
133
|
+
continue
|
|
134
|
+
else:
|
|
135
|
+
# Debug: print raw data
|
|
136
|
+
print(f"[debug: {data}]", end="", flush=True)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Close thinking if switching
|
|
140
|
+
if msg_type != "thinking" and in_thinking:
|
|
141
|
+
print(f"</thinking>{RESET}\n", end="", flush=True)
|
|
142
|
+
in_thinking = False
|
|
143
|
+
|
|
144
|
+
# Flush table if switching
|
|
145
|
+
if msg_type != "table" and table_headers:
|
|
146
|
+
render_table(table_headers, table_rows)
|
|
147
|
+
table_headers = []
|
|
148
|
+
table_rows = []
|
|
149
|
+
|
|
150
|
+
if msg_type == "thinking":
|
|
151
|
+
if not in_thinking:
|
|
152
|
+
print(f"{DIM}<thinking>", end="", flush=True)
|
|
153
|
+
in_thinking = True
|
|
154
|
+
print(data.get("thinking", ""), end="", flush=True)
|
|
155
|
+
|
|
156
|
+
elif msg_type == "text":
|
|
157
|
+
print(data.get("text", ""), end="", flush=True)
|
|
158
|
+
|
|
159
|
+
elif msg_type == "code":
|
|
160
|
+
lang = data.get("language", "")
|
|
161
|
+
print(f"\n```{lang}\n{data.get('code', '')}\n```\n", end="", flush=True)
|
|
162
|
+
|
|
163
|
+
elif msg_type == "status":
|
|
164
|
+
print(f"{DIM}[{data.get('status', '')}]{RESET} ", end="", flush=True)
|
|
165
|
+
|
|
166
|
+
elif msg_type == "table":
|
|
167
|
+
if "headers" in data:
|
|
168
|
+
if table_headers:
|
|
169
|
+
render_table(table_headers, table_rows)
|
|
170
|
+
table_headers = data["headers"]
|
|
171
|
+
table_rows = []
|
|
172
|
+
elif "row" in data:
|
|
173
|
+
table_rows.append(data["row"])
|
|
174
|
+
|
|
175
|
+
elif msg_type == "callout":
|
|
176
|
+
style = data.get("style", "info")
|
|
177
|
+
icon, color = CALLOUT_STYLES.get(style, ("•", RESET))
|
|
178
|
+
title = data.get("title", "")
|
|
179
|
+
text = data.get("callout", "")
|
|
180
|
+
if title:
|
|
181
|
+
print(f"\n{color}{icon} {BOLD}{title}{RESET}")
|
|
182
|
+
print(f"{color} {text}{RESET}\n", end="", flush=True)
|
|
183
|
+
else:
|
|
184
|
+
print(f"\n{color}{icon} {text}{RESET}\n", end="", flush=True)
|
|
185
|
+
|
|
186
|
+
elif msg_type == "image":
|
|
187
|
+
print(f"{DIM}[image: {data.get('src', '')}]{RESET}", end="", flush=True)
|
|
188
|
+
|
|
189
|
+
# Flush remaining
|
|
190
|
+
if table_headers:
|
|
191
|
+
render_table(table_headers, table_rows)
|
|
192
|
+
if in_thinking:
|
|
193
|
+
print(f"</thinking>{RESET}", end="", flush=True)
|
|
194
|
+
|
|
195
|
+
elapsed = time.time() - start_time
|
|
196
|
+
print(f"\n\n{DIM}✦ {format_time(elapsed)}{RESET}\n")
|
|
197
|
+
|
|
198
|
+
except KeyboardInterrupt:
|
|
199
|
+
continue
|
|
200
|
+
except EOFError:
|
|
201
|
+
print(f"{RESET}\n👋")
|
|
202
|
+
break
|
|
203
|
+
except (httpx.ReadError, httpx.ConnectError):
|
|
204
|
+
print(f"{RESET}🔄 Reconnecting...", end="", flush=True)
|
|
205
|
+
time.sleep(1)
|
|
206
|
+
print(CLEAR_LINE, end="")
|
|
207
|
+
if messages:
|
|
208
|
+
messages.pop()
|
|
209
|
+
|
|
210
|
+
def main():
|
|
211
|
+
if len(sys.argv) < 3 or sys.argv[1] != "chat":
|
|
212
|
+
print("Usage: cycls chat <url>")
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
chat(sys.argv[2])
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
main()
|
cycls/runtime.py
CHANGED
|
@@ -337,6 +337,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
337
337
|
return
|
|
338
338
|
|
|
339
339
|
import inspect
|
|
340
|
+
import subprocess
|
|
340
341
|
|
|
341
342
|
# Get the main script (the outermost .py file in the stack)
|
|
342
343
|
main_script = None
|
|
@@ -362,75 +363,37 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
362
363
|
print()
|
|
363
364
|
|
|
364
365
|
while True:
|
|
365
|
-
# Run the
|
|
366
|
-
print(f"🚀 Running
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
ports_mapping = {f'{port}/tcp': port} if port else None
|
|
372
|
-
|
|
373
|
-
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
374
|
-
tmpdir = Path(tmpdir_str)
|
|
375
|
-
payload_path = tmpdir / self.payload_file
|
|
376
|
-
|
|
377
|
-
with payload_path.open('wb') as f:
|
|
378
|
-
cloudpickle.dump((self.func, args, kwargs), f)
|
|
366
|
+
# Run the script in a subprocess so we survive errors
|
|
367
|
+
print(f"🚀 Running {main_script.name}...")
|
|
368
|
+
proc = subprocess.Popen(
|
|
369
|
+
[sys.executable, str(main_script)],
|
|
370
|
+
env={**os.environ, '_CYCLS_WATCH_CHILD': '1'}
|
|
371
|
+
)
|
|
379
372
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
373
|
+
try:
|
|
374
|
+
# Watch for changes
|
|
375
|
+
for changes in watchfiles_watch(*watch_paths):
|
|
376
|
+
changed_files = [str(c[1]) for c in changes]
|
|
377
|
+
print(f"\n🔄 Changes detected:")
|
|
378
|
+
for f in changed_files:
|
|
379
|
+
print(f" {f}")
|
|
380
|
+
break
|
|
381
|
+
|
|
382
|
+
print("\n🔄 Restarting...\n")
|
|
383
|
+
proc.terminate()
|
|
384
|
+
try:
|
|
385
|
+
proc.wait(timeout=3)
|
|
386
|
+
except subprocess.TimeoutExpired:
|
|
387
|
+
proc.kill()
|
|
389
388
|
|
|
389
|
+
except KeyboardInterrupt:
|
|
390
|
+
print("\n🛑 Stopping...")
|
|
391
|
+
proc.terminate()
|
|
390
392
|
try:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
def watch_thread():
|
|
397
|
-
for changes in watchfiles_watch(*watch_paths):
|
|
398
|
-
changed_files = [str(c[1]) for c in changes]
|
|
399
|
-
print(f"\n🔄 Changes detected:")
|
|
400
|
-
for f in changed_files:
|
|
401
|
-
print(f" {f}")
|
|
402
|
-
change_detected.set()
|
|
403
|
-
break
|
|
404
|
-
|
|
405
|
-
watcher = threading.Thread(target=watch_thread, daemon=True)
|
|
406
|
-
watcher.start()
|
|
407
|
-
|
|
408
|
-
# Stream container logs in separate thread
|
|
409
|
-
def log_thread():
|
|
410
|
-
for chunk in container.logs(stream=True, follow=True):
|
|
411
|
-
if change_detected.is_set():
|
|
412
|
-
break
|
|
413
|
-
print(chunk.decode('utf-8').strip())
|
|
414
|
-
|
|
415
|
-
logger = threading.Thread(target=log_thread, daemon=True)
|
|
416
|
-
logger.start()
|
|
417
|
-
|
|
418
|
-
# Wait for change or interrupt
|
|
419
|
-
while not change_detected.is_set():
|
|
420
|
-
time.sleep(0.5)
|
|
421
|
-
|
|
422
|
-
print("\n🔄 Restarting...")
|
|
423
|
-
container.stop(timeout=2)
|
|
424
|
-
container.remove()
|
|
425
|
-
# Re-exec the main script
|
|
426
|
-
print(f"🔄 Re-executing {main_script.name}...\n")
|
|
427
|
-
os.execv(sys.executable, [sys.executable, str(main_script)])
|
|
428
|
-
|
|
429
|
-
except KeyboardInterrupt:
|
|
430
|
-
print("\n🛑 Stopping...")
|
|
431
|
-
container.stop(timeout=2)
|
|
432
|
-
container.remove()
|
|
433
|
-
return
|
|
393
|
+
proc.wait(timeout=3)
|
|
394
|
+
except subprocess.TimeoutExpired:
|
|
395
|
+
proc.kill()
|
|
396
|
+
return
|
|
434
397
|
|
|
435
398
|
def build(self, *args, **kwargs):
|
|
436
399
|
"""Builds a self-contained, deployable Docker image locally."""
|
cycls/sdk.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import time, inspect, uvicorn
|
|
1
|
+
import os, time, inspect, uvicorn
|
|
2
2
|
from .runtime import Runtime
|
|
3
3
|
from .web import web, Config
|
|
4
4
|
from .auth import PK_LIVE, PK_TEST, JWKS_PROD, JWKS_TEST
|
|
@@ -74,7 +74,8 @@ class Agent:
|
|
|
74
74
|
return f
|
|
75
75
|
return decorator
|
|
76
76
|
|
|
77
|
-
def
|
|
77
|
+
def _local(self, port=8080, watch=True):
|
|
78
|
+
"""Run directly with uvicorn (no Docker)."""
|
|
78
79
|
if not self.registered_functions:
|
|
79
80
|
print("Error: No @agent decorated function found.")
|
|
80
81
|
return
|
|
@@ -85,19 +86,14 @@ class Agent:
|
|
|
85
86
|
print(f"🚀 Starting local server at localhost:{port}")
|
|
86
87
|
agent.config.public_path = self.theme
|
|
87
88
|
set_prod(agent.config, False)
|
|
88
|
-
uvicorn.run(web(agent.func, agent.config), host="0.0.0.0", port=port)
|
|
89
|
+
uvicorn.run(web(agent.func, agent.config), host="0.0.0.0", port=port, reload=watch)
|
|
89
90
|
return
|
|
90
91
|
|
|
91
|
-
def
|
|
92
|
+
def _runtime(self, prod=False):
|
|
93
|
+
"""Create a Runtime instance for the first registered agent."""
|
|
92
94
|
if not self.registered_functions:
|
|
93
95
|
print("Error: No @agent decorated function found.")
|
|
94
|
-
return
|
|
95
|
-
if (self.key is None) and prod:
|
|
96
|
-
print("🛑 Error: Please add your Cycls API key")
|
|
97
|
-
return
|
|
98
|
-
if prod and watch:
|
|
99
|
-
print("⚠️ Warning: watch=True ignored in production mode.")
|
|
100
|
-
watch = False
|
|
96
|
+
return None
|
|
101
97
|
|
|
102
98
|
agent = self.registered_functions[0]
|
|
103
99
|
if len(self.registered_functions) > 1:
|
|
@@ -112,7 +108,7 @@ class Agent:
|
|
|
112
108
|
files.update({f: f for f in self.copy})
|
|
113
109
|
files.update({f: f"public/{f}" for f in self.copy_public})
|
|
114
110
|
|
|
115
|
-
|
|
111
|
+
return Runtime(
|
|
116
112
|
func=lambda port: __import__("web").serve(func, config_dict, name, port),
|
|
117
113
|
name=name,
|
|
118
114
|
apt_packages=self.apt,
|
|
@@ -121,13 +117,24 @@ class Agent:
|
|
|
121
117
|
base_url=self.base_url,
|
|
122
118
|
api_key=self.key
|
|
123
119
|
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
|
|
121
|
+
def local(self, port=8080, watch=True):
|
|
122
|
+
"""Run locally in Docker with file watching by default."""
|
|
123
|
+
# Child process spawned by watcher - run without watch
|
|
124
|
+
if os.environ.get('_CYCLS_WATCH_CHILD'):
|
|
125
|
+
watch = False
|
|
126
|
+
runtime = self._runtime(prod=False)
|
|
127
|
+
if runtime:
|
|
128
|
+
runtime.watch(port=port) if watch else runtime.run(port=port)
|
|
129
|
+
|
|
130
|
+
def deploy(self, port=8080):
|
|
131
|
+
"""Deploy to production."""
|
|
132
|
+
if self.key is None:
|
|
133
|
+
print("🛑 Error: Please add your Cycls API key")
|
|
134
|
+
return
|
|
135
|
+
runtime = self._runtime(prod=True)
|
|
136
|
+
if runtime:
|
|
137
|
+
runtime.deploy(port=port)
|
|
131
138
|
|
|
132
139
|
def modal(self, prod=False):
|
|
133
140
|
import modal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cycls
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.71
|
|
4
4
|
Summary: Distribute Intelligence
|
|
5
5
|
Author: Mohammed J. AlRujayi
|
|
6
6
|
Author-email: mj@cycls.com
|
|
@@ -70,7 +70,7 @@ async def chat(context):
|
|
|
70
70
|
if chunk.choices[0].delta.content:
|
|
71
71
|
yield chunk.choices[0].delta.content
|
|
72
72
|
|
|
73
|
-
agent.deploy(
|
|
73
|
+
agent.deploy() # Live at https://my-agent.cycls.ai
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
## Installation
|
|
@@ -90,11 +90,12 @@ Requires Docker.
|
|
|
90
90
|
- **Monetization** - `tier="cycls_pass"` integrates with [Cycls Pass](https://cycls.ai) subscriptions
|
|
91
91
|
- **Native UI Components** - Render thinking bubbles, tables, code blocks in responses
|
|
92
92
|
|
|
93
|
-
##
|
|
93
|
+
## Running
|
|
94
94
|
|
|
95
95
|
```python
|
|
96
|
-
agent.
|
|
97
|
-
agent.
|
|
96
|
+
agent.local() # Development with hot-reload (localhost:8080)
|
|
97
|
+
agent.local(watch=False) # Development without hot-reload
|
|
98
|
+
agent.deploy() # Production: https://agent-name.cycls.ai
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
Get an API key at [cycls.com](https://cycls.com).
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
cycls/__init__.py,sha256=bVT0dYTXLdSC3ZURgtm-DEOj-VO6RUM6zGsJB0zuj6Y,61
|
|
2
2
|
cycls/auth.py,sha256=xkndHZyCfnlertMMEKerCJjf23N3fVcTRVTTSXTTuzg,247
|
|
3
|
+
cycls/cli.py,sha256=AKf0z7ZLau3GvBVR_IhB7agmq4nVaHkcuUafNyvv2_A,7978
|
|
3
4
|
cycls/default-theme/assets/index-B0ZKcm_V.css,sha256=wK9-NhEB8xPcN9Zv69zpOcfGTlFbMwyC9WqTmSKUaKw,6546
|
|
4
5
|
cycls/default-theme/assets/index-D5EDcI4J.js,sha256=sN4qRcAXa7DBd9JzmVcCoCwH4l8cNCM-U9QGUjBvWSo,1346506
|
|
5
6
|
cycls/default-theme/index.html,sha256=bM-yW_g0cGrV40Q5yY3ccY0fM4zI1Wuu5I8EtGFJIxs,828
|
|
6
7
|
cycls/dev-theme/index.html,sha256=QJBHkdNuMMiwQU7o8dN8__8YQeQB45D37D-NCXIWB2Q,11585
|
|
7
|
-
cycls/runtime.py,sha256=
|
|
8
|
-
cycls/sdk.py,sha256=
|
|
8
|
+
cycls/runtime.py,sha256=LgOrQ6Arh-GWSJqckfra-CvjeSekTvGvjHOBxu7JTQQ,21408
|
|
9
|
+
cycls/sdk.py,sha256=6oRKP44TJN9HKdNw9OYzDlZFDUMUhoCMt8TEwyu26dI,7368
|
|
9
10
|
cycls/web.py,sha256=3M3qaWTNY3dpgd7Vq5aXREp-cIFsHrDqBQ1YkGrOaUk,4659
|
|
10
|
-
cycls-0.0.2.
|
|
11
|
-
cycls-0.0.2.
|
|
12
|
-
cycls-0.0.2.
|
|
11
|
+
cycls-0.0.2.71.dist-info/METADATA,sha256=MG66f3JLtPXNMLV1rCs29BYkBxk2B3aMIG5_azsD9HI,8008
|
|
12
|
+
cycls-0.0.2.71.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
13
|
+
cycls-0.0.2.71.dist-info/entry_points.txt,sha256=vEhqUxFhhuzCKWtq02LbMnT3wpUqdfgcM3Yh-jjXom8,40
|
|
14
|
+
cycls-0.0.2.71.dist-info/RECORD,,
|
|
File without changes
|