hypercli-sdk 0.4.2__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.
c3/renders.py ADDED
@@ -0,0 +1,339 @@
1
+ """Renders API"""
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, List, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from .http import HTTPClient
7
+
8
+
9
+ @dataclass
10
+ class Render:
11
+ render_id: str
12
+ state: str
13
+ template: str | None = None
14
+ render_type: str | None = None
15
+ result_url: str | None = None
16
+ error: str | None = None
17
+ created_at: float | None = None
18
+ started_at: float | None = None
19
+ completed_at: float | None = None
20
+
21
+ @classmethod
22
+ def from_dict(cls, data: dict) -> "Render":
23
+ return cls(
24
+ render_id=data.get("id") or data.get("render_id", ""),
25
+ state=data.get("state", ""),
26
+ template=data.get("template") or data.get("meta", {}).get("template"),
27
+ render_type=data.get("type") or data.get("render_type"),
28
+ result_url=data.get("result_url"),
29
+ error=data.get("error"),
30
+ created_at=data.get("created_at"),
31
+ started_at=data.get("started_at"),
32
+ completed_at=data.get("completed_at"),
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class RenderStatus:
38
+ render_id: str
39
+ state: str
40
+ progress: float | None = None
41
+
42
+ @classmethod
43
+ def from_dict(cls, data: dict) -> "RenderStatus":
44
+ return cls(
45
+ render_id=data.get("id") or data.get("render_id", ""),
46
+ state=data.get("state", ""),
47
+ progress=data.get("progress"),
48
+ )
49
+
50
+
51
+ class Renders:
52
+ """Renders API wrapper"""
53
+
54
+ def __init__(self, http: "HTTPClient"):
55
+ self._http = http
56
+
57
+ def list(
58
+ self,
59
+ state: str = None,
60
+ template: str = None,
61
+ type: str = None,
62
+ ) -> list[Render]:
63
+ """List all renders.
64
+
65
+ Args:
66
+ state: Filter by state (e.g., "pending", "running", "completed")
67
+ template: Filter by template name
68
+ type: Filter by render type (e.g., "comfyui")
69
+ """
70
+ params = {}
71
+ if state:
72
+ params["state"] = state
73
+ if template:
74
+ params["template"] = template
75
+ if type:
76
+ params["type"] = type
77
+
78
+ data = self._http.get("/api/renders", params=params or None)
79
+ # Handle paginated response
80
+ items = data.get("items", data) if isinstance(data, dict) else data
81
+ return [Render.from_dict(r) for r in items]
82
+
83
+ def get(self, render_id: str) -> Render:
84
+ """Get render details"""
85
+ data = self._http.get(f"/api/renders/{render_id}")
86
+ return Render.from_dict(data)
87
+
88
+ def create(
89
+ self,
90
+ params: dict,
91
+ render_type: str = "comfyui",
92
+ notify_url: str = None,
93
+ ) -> Render:
94
+ """Create a new render.
95
+
96
+ Args:
97
+ params: Render parameters (workflow-specific)
98
+ render_type: Type of render (default: "comfyui")
99
+ notify_url: Optional webhook URL for completion notification
100
+ """
101
+ payload = {
102
+ "type": render_type,
103
+ "params": params,
104
+ }
105
+ if notify_url:
106
+ payload["notify_url"] = notify_url
107
+
108
+ data = self._http.post("/api/renders", json=payload)
109
+ return Render.from_dict(data)
110
+
111
+ def cancel(self, render_id: str) -> dict:
112
+ """Cancel a render"""
113
+ return self._http.delete(f"/api/renders/{render_id}")
114
+
115
+ def status(self, render_id: str) -> RenderStatus:
116
+ """Get render status (lightweight polling endpoint)"""
117
+ data = self._http.get(f"/api/renders/{render_id}/status")
118
+ return RenderStatus.from_dict(data)
119
+
120
+ # =========================================================================
121
+ # Flow endpoints - simplified interfaces
122
+ # =========================================================================
123
+
124
+ def _flow(self, endpoint: str, **kwargs) -> Render:
125
+ """Helper for flow endpoints. Filters None values from payload."""
126
+ payload = {k: v for k, v in kwargs.items() if v is not None}
127
+ data = self._http.post(endpoint, json=payload)
128
+ return Render.from_dict(data)
129
+
130
+ def text_to_image(
131
+ self,
132
+ prompt: str,
133
+ negative: str = None,
134
+ width: int = None,
135
+ height: int = None,
136
+ notify_url: str = None,
137
+ ) -> Render:
138
+ """Generate an image using Qwen-Image (great for text in images).
139
+
140
+ Args:
141
+ prompt: Text description of the image
142
+ negative: Optional negative prompt (things to avoid)
143
+ width: Optional output width
144
+ height: Optional output height
145
+ notify_url: Optional webhook URL for completion notification
146
+
147
+ Example:
148
+ render = c3.renders.text_to_image("a cat wearing sunglasses")
149
+ """
150
+ return self._flow("/api/flow/text-to-image", prompt=prompt, negative=negative, width=width, height=height, notify_url=notify_url)
151
+
152
+ def text_to_image_hidream(
153
+ self,
154
+ prompt: str,
155
+ negative: str = None,
156
+ width: int = None,
157
+ height: int = None,
158
+ notify_url: str = None,
159
+ ) -> Render:
160
+ """Generate an image using HiDream I1 Full (highest quality).
161
+
162
+ Args:
163
+ prompt: Text description of the image
164
+ negative: Optional negative prompt (things to avoid)
165
+ width: Optional output width
166
+ height: Optional output height
167
+ notify_url: Optional webhook URL for completion notification
168
+
169
+ Example:
170
+ render = c3.renders.text_to_image_hidream("a mystical forest")
171
+ """
172
+ return self._flow("/api/flow/text-to-image-hidream", prompt=prompt, negative=negative, width=width, height=height, notify_url=notify_url)
173
+
174
+ def text_to_video(
175
+ self,
176
+ prompt: str,
177
+ negative: str = None,
178
+ width: int = None,
179
+ height: int = None,
180
+ notify_url: str = None,
181
+ ) -> Render:
182
+ """Generate a video using Wan 2.2 14B.
183
+
184
+ Args:
185
+ prompt: Text description of the video
186
+ negative: Optional negative prompt (things to avoid)
187
+ width: Optional video width
188
+ height: Optional video height
189
+ notify_url: Optional webhook URL for completion notification
190
+
191
+ Example:
192
+ render = c3.renders.text_to_video("a cat walking through a garden")
193
+ """
194
+ return self._flow("/api/flow/text-to-video", prompt=prompt, negative=negative, width=width, height=height, notify_url=notify_url)
195
+
196
+ def image_to_video(
197
+ self,
198
+ prompt: str,
199
+ image_url: str,
200
+ negative: str = None,
201
+ width: int = None,
202
+ height: int = None,
203
+ notify_url: str = None,
204
+ ) -> Render:
205
+ """Animate an image using Wan 2.2 Animate.
206
+
207
+ Args:
208
+ prompt: Description of the motion/animation
209
+ image_url: URL of the image to animate
210
+ negative: Optional negative prompt (things to avoid)
211
+ width: Optional video width
212
+ height: Optional video height
213
+ notify_url: Optional webhook URL for completion notification
214
+
215
+ Example:
216
+ render = c3.renders.image_to_video("dancing", "https://example.com/img.png", width=832, height=480)
217
+ """
218
+ return self._flow("/api/flow/image-to-video", prompt=prompt, image_url=image_url, negative=negative, width=width, height=height, notify_url=notify_url)
219
+
220
+ def speaking_video(
221
+ self,
222
+ prompt: str,
223
+ image_url: str,
224
+ audio_url: str,
225
+ negative: str = None,
226
+ width: int = None,
227
+ height: int = None,
228
+ notify_url: str = None,
229
+ ) -> Render:
230
+ """Generate a lip-sync video using HuMo.
231
+
232
+ Args:
233
+ prompt: Description of the scene/character
234
+ image_url: URL of the face/character image
235
+ audio_url: URL of the audio/speech file
236
+ negative: Optional negative prompt (things to avoid)
237
+ width: Optional video width
238
+ height: Optional video height
239
+ notify_url: Optional webhook URL for completion notification
240
+
241
+ Example:
242
+ render = c3.renders.speaking_video(
243
+ "A person talking to camera",
244
+ "https://example.com/face.png",
245
+ "https://example.com/speech.mp3"
246
+ )
247
+ """
248
+ return self._flow("/api/flow/speaking-video", prompt=prompt, image_url=image_url, audio_url=audio_url, negative=negative, width=width, height=height, notify_url=notify_url)
249
+
250
+ def speaking_video_wan(
251
+ self,
252
+ prompt: str,
253
+ image_url: str,
254
+ audio_url: str,
255
+ negative: str = None,
256
+ width: int = None,
257
+ height: int = None,
258
+ notify_url: str = None,
259
+ ) -> Render:
260
+ """Generate an audio-driven video using Wan 2.2 S2V.
261
+
262
+ Args:
263
+ prompt: Description of the scene/action
264
+ image_url: URL of the image
265
+ audio_url: URL of the audio file
266
+ negative: Optional negative prompt (things to avoid)
267
+ width: Optional video width
268
+ height: Optional video height
269
+ notify_url: Optional webhook URL for completion notification
270
+
271
+ Example:
272
+ render = c3.renders.speaking_video_wan(
273
+ "The person is singing",
274
+ "https://example.com/face.png",
275
+ "https://example.com/song.mp3"
276
+ )
277
+ """
278
+ return self._flow("/api/flow/speaking-video-wan", prompt=prompt, image_url=image_url, audio_url=audio_url, negative=negative, width=width, height=height, notify_url=notify_url)
279
+
280
+ def image_to_image(
281
+ self,
282
+ prompt: str,
283
+ image_urls: List[str],
284
+ negative: str = None,
285
+ width: int = None,
286
+ height: int = None,
287
+ notify_url: str = None,
288
+ ) -> Render:
289
+ """Transform images using Qwen Image Edit with 1-3 input images.
290
+
291
+ Args:
292
+ prompt: Description of the transformation
293
+ image_urls: List of 1-3 image URLs (first is main, others are references)
294
+ negative: Optional negative prompt (things to avoid)
295
+ width: Optional output width
296
+ height: Optional output height
297
+ notify_url: Optional webhook URL for completion notification
298
+
299
+ Example:
300
+ render = c3.renders.image_to_image(
301
+ "Apply the artistic style from the references",
302
+ [
303
+ "https://example.com/subject.jpg",
304
+ "https://example.com/style1.jpg",
305
+ "https://example.com/style2.jpg",
306
+ ]
307
+ )
308
+ """
309
+ return self._flow("/api/flow/image-to-image", prompt=prompt, image_urls=image_urls, negative=negative, width=width, height=height, notify_url=notify_url)
310
+
311
+ def first_last_frame_video(
312
+ self,
313
+ prompt: str,
314
+ start_image_url: str,
315
+ end_image_url: str,
316
+ negative: str = None,
317
+ width: int = None,
318
+ height: int = None,
319
+ notify_url: str = None,
320
+ ) -> Render:
321
+ """Generate video morphing between two images using Wan 2.2.
322
+
323
+ Args:
324
+ prompt: Description of the transition/motion
325
+ start_image_url: URL of the starting frame
326
+ end_image_url: URL of the ending frame
327
+ negative: Optional negative prompt (things to avoid)
328
+ width: Optional video width
329
+ height: Optional video height
330
+ notify_url: Optional webhook URL for completion notification
331
+
332
+ Example:
333
+ render = c3.renders.first_last_frame_video(
334
+ "smooth transition from day to night",
335
+ "https://example.com/day.png",
336
+ "https://example.com/night.png"
337
+ )
338
+ """
339
+ return self._flow("/api/flow/first-last-frame-video", prompt=prompt, start_image_url=start_image_url, end_image_url=end_image_url, negative=negative, width=width, height=height, notify_url=notify_url)
c3/user.py ADDED
@@ -0,0 +1,37 @@
1
+ """User API"""
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .http import HTTPClient
7
+
8
+
9
+ @dataclass
10
+ class User:
11
+ user_id: str
12
+ email: str | None
13
+ name: str | None
14
+ is_active: bool
15
+ created_at: str
16
+
17
+ @classmethod
18
+ def from_dict(cls, data: dict) -> "User":
19
+ return cls(
20
+ user_id=data.get("user_id", ""),
21
+ email=data.get("email"),
22
+ name=data.get("name"),
23
+ is_active=data.get("is_active", True),
24
+ created_at=data.get("created_at", ""),
25
+ )
26
+
27
+
28
+ class UserAPI:
29
+ """User API wrapper"""
30
+
31
+ def __init__(self, http: "HTTPClient"):
32
+ self._http = http
33
+
34
+ def get(self) -> User:
35
+ """Get current user info"""
36
+ data = self._http.get("/api/user")
37
+ return User.from_dict(data)
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: hypercli-sdk
3
+ Version: 0.4.2
4
+ Summary: Python SDK for HyperCLI - GPU orchestration and LLM API
5
+ Project-URL: Homepage, https://hypercli.com
6
+ Project-URL: Documentation, https://docs.hypercli.com
7
+ Project-URL: Repository, https://github.com/hypercli/sdk-python
8
+ Author-email: HyperCLI <support@hypercli.com>
9
+ License-Expression: MIT
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: httpx>=0.28.1
18
+ Requires-Dist: websockets>=15.0.1
19
+ Provides-Extra: comfyui
20
+ Requires-Dist: comfyui-workflow-templates-media-image>=0.3.0; extra == 'comfyui'
21
+ Requires-Dist: comfyui-workflow-templates>=0.7.0; extra == 'comfyui'
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.3.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # C3 SDK
29
+
30
+ Python SDK for [HyperCLI](https://hypercli.com) - GPU orchestration API.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install c3-sdk
36
+ ```
37
+
38
+ ## Setup
39
+
40
+ Set your API key:
41
+
42
+ ```bash
43
+ export C3_API_KEY=your_api_key
44
+ ```
45
+
46
+ Or create `~/.c3/config`:
47
+ ```
48
+ C3_API_KEY=your_api_key
49
+ ```
50
+
51
+ Or pass directly:
52
+ ```python
53
+ c3 = C3(api_key="your_api_key")
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ```python
59
+ from c3 import C3
60
+
61
+ c3 = C3()
62
+
63
+ # Check balance
64
+ balance = c3.billing.balance()
65
+ print(f"Balance: ${balance.total:.2f}")
66
+ print(f"Rewards: ${balance.rewards:.2f}")
67
+
68
+ # List transactions
69
+ for tx in c3.billing.transactions(limit=10):
70
+ print(f"{tx.transaction_type}: ${tx.amount_usd:.4f}")
71
+
72
+ # Create a job
73
+ job = c3.jobs.create(
74
+ image="nvidia/cuda:12.0",
75
+ command="python train.py",
76
+ gpu_type="l40s",
77
+ gpu_count=1,
78
+ )
79
+ print(f"Job ID: {job.job_id}")
80
+ print(f"State: {job.state}")
81
+
82
+ # List jobs
83
+ for job in c3.jobs.list():
84
+ print(f"{job.job_id}: {job.state}")
85
+
86
+ # Get job details
87
+ job = c3.jobs.get("job_id")
88
+
89
+ # Get job logs
90
+ logs = c3.jobs.logs("job_id")
91
+
92
+ # Get GPU metrics
93
+ metrics = c3.jobs.metrics("job_id")
94
+ for gpu in metrics.gpus:
95
+ print(f"GPU {gpu.index}: {gpu.utilization}% util, {gpu.temperature}°C")
96
+
97
+ # Cancel a job
98
+ c3.jobs.cancel("job_id")
99
+
100
+ # Extend runtime
101
+ c3.jobs.extend("job_id", runtime=7200)
102
+
103
+ # Get user info
104
+ user = c3.user.get()
105
+ print(f"User: {user.email}")
106
+ ```
107
+
108
+ ## LLM API
109
+
110
+ For LLM access, use the OpenAI SDK with C3's base URL:
111
+
112
+ ```python
113
+ from openai import OpenAI
114
+
115
+ client = OpenAI(
116
+ api_key="your_c3_api_key",
117
+ base_url="https://api.hypercli.com/v1"
118
+ )
119
+
120
+ response = client.chat.completions.create(
121
+ model="deepseek-v3.1",
122
+ messages=[{"role": "user", "content": "Hello!"}]
123
+ )
124
+ ```
125
+
126
+ ## Error Handling
127
+
128
+ ```python
129
+ from c3 import C3, APIError
130
+
131
+ c3 = C3()
132
+
133
+ try:
134
+ job = c3.jobs.get("invalid_id")
135
+ except APIError as e:
136
+ print(f"Error {e.status_code}: {e.detail}")
137
+ ```
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,17 @@
1
+ c3/__init__.py,sha256=d3rSgVM3gYEtqlz11gkJY-gNQ3IDd_MO609TxDlOWFk,1431
2
+ c3/billing.py,sha256=zktogvY4XJhJKuQwq0OQcOM_R36rDRJ-jkDwQdkz9qI,2014
3
+ c3/client.py,sha256=hB2JiXNIZu24s5OsPGOrVgbLWt2sNh2VP-bvJxmoVJo,1578
4
+ c3/config.py,sha256=hh3u6-Iewt4nY2gKREOSwRAWl24rmdEYoZvs-Jv_sJs,1998
5
+ c3/files.py,sha256=ipkzs9L1FlgNY_HLdZEGovpfI9Gh4C6yRX4wvE3rbiM,12504
6
+ c3/http.py,sha256=uzxaEI_q1OPv75g56WNCCtPOcmuRsbsrbvvFJrUnyik,7215
7
+ c3/instances.py,sha256=d_zw_JbxL0XPf2kroVtjCK4fjvBx_sBIRMqKP-_kdeQ,7112
8
+ c3/jobs.py,sha256=o1I9cS3QRZiGGw9q-rZ_RwcfTNZxCmRbLdxpb4PxeK8,8307
9
+ c3/logs.py,sha256=tBRIk3ymWcKjEP7EsYVJFZlNRBlZfvEo5JR8mlEVbi4,8956
10
+ c3/renders.py,sha256=rJVS2rP19V1XLX-MBHsM2y_g_fdXnCBErO_oBsZzSTI,11871
11
+ c3/user.py,sha256=Fbzxq64HgDSiP8KyyocKdYVLrTZjFet9LbxCQLhKgDg,830
12
+ c3/job/__init__.py,sha256=q7qh7HyPuYdbMGE-ZPbqN7ss7-irlhLcASNH6K0PllE,448
13
+ c3/job/base.py,sha256=8tvkM1ebhQdNIpFPe6431gkKsCVOLKJXXWQRYB_Iz_g,8529
14
+ c3/job/comfyui.py,sha256=NIcTkCrHMcDxKC5v68Whbgmb0_T8hbYAAGhCRUthjEw,58817
15
+ hypercli_sdk-0.4.2.dist-info/METADATA,sha256=UL5AfalUjQW_vasMH697gfCGeYSPR07FGyrdHx-coys,2970
16
+ hypercli_sdk-0.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ hypercli_sdk-0.4.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any