everypixel-cli 0.2.2__tar.gz → 0.4.0__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.
Files changed (25) hide show
  1. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/CHANGELOG.md +10 -0
  2. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/PKG-INFO +68 -6
  3. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/README.md +67 -5
  4. everypixel_cli-0.4.0/src/everypixel_cli/__init__.py +1 -0
  5. everypixel_cli-0.4.0/src/everypixel_cli/application/chat.py +192 -0
  6. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/application/services.py +245 -73
  7. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/cli.py +191 -21
  8. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/client.py +89 -17
  9. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/mcp_server.py +98 -19
  10. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/output.py +55 -0
  11. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/resources/openapi.json +942 -54
  12. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/schemas.py +432 -19
  13. everypixel_cli-0.2.2/src/everypixel_cli/__init__.py +0 -1
  14. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/.gitignore +0 -0
  15. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/LICENSE +0 -0
  16. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/pyproject.toml +0 -0
  17. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/__main__.py +0 -0
  18. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/application/__init__.py +0 -0
  19. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/application/models.py +0 -0
  20. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/application/serialization.py +0 -0
  21. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/config.py +0 -0
  22. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/errors.py +0 -0
  23. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/files.py +0 -0
  24. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/openapi.py +0 -0
  25. {everypixel_cli-0.2.2 → everypixel_cli-0.4.0}/src/everypixel_cli/resources/__init__.py +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0] - 2026-10-07
4
+
5
+ - Add ElevenLabs speech synthesis and voice design.
6
+ - Add saved voice creation, listing, deletion, and Qwen cloning by voice ID.
7
+
8
+ ## [0.3.0] - 2026-09-18
9
+
10
+ - Add GPT Image 2.5 Sunburst and quality selection for GPT Image models.
11
+ - Add GLM-5.3 chat with streaming and tool-call support.
12
+
3
13
  ## [0.2.2] - 2026-09-11
4
14
 
5
15
  - Increase the default task wait timeout to 30 minutes for long video generation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: everypixel-cli
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Official command line interface for Everypixel API
5
5
  Project-URL: Homepage, https://labs.everypixel.com
6
6
  Project-URL: Repository, https://github.com/everypixel/everypixel-cli
@@ -35,8 +35,8 @@ Description-Content-Type: text/markdown
35
35
  # Everypixel CLI
36
36
 
37
37
  Official command-line interface for the [Everypixel API](https://labs.everypixel.com/).
38
- Generate and edit images and videos, work with audio, run visual analysis, and
39
- automate Everypixel workflows from a terminal or an MCP client.
38
+ Generate and edit images and videos, chat with GLM-5.3, work with audio, run
39
+ visual analysis, and automate Everypixel workflows from a terminal or an MCP client.
40
40
 
41
41
  The CLI is designed for both people and agents: commands use readable terminal
42
42
  output by default and can return stable JSON for scripts and tool integrations.
@@ -181,6 +181,15 @@ to select their aspect ratio. Raster models also accept `--resolution`.
181
181
 
182
182
  `--lora-url` is supported only by `zimage`.
183
183
 
184
+ GPT Image models are `gpt-image-2` and `gpt-image-2.5-sunburst`. Both generation
185
+ and editing accept `--quality low|medium|high` (default: `medium`); Sunburst also
186
+ supports `xhigh` and `max`. Use `--size square` with `--resolution 3k`.
187
+
188
+ ```bash
189
+ everypixel image generate "a detailed botanical illustration" \
190
+ --model gpt-image-2.5-sunburst --quality high --download ./outputs
191
+ ```
192
+
184
193
  Grok image models are `grok-imagine`, `grok-imagine-2`, and
185
194
  `grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
186
195
  `wan22` have been removed from the API.
@@ -254,9 +263,12 @@ They support 4K output. The default `seedvr2` supports up to 1440p, with a
254
263
  | `everypixel lipsync video` | Synchronize a video with an audio track |
255
264
  | `everypixel lipsync image` | Create a lipsync video from a still image and audio |
256
265
  | `everypixel audio transcribe` | Transcribe speech from an audio file |
257
- | `everypixel audio tts-create` | Create speech with a built-in speaker |
258
- | `everypixel audio tts-clone` | Create speech from a cloned voice sample |
259
- | `everypixel audio tts-voice` | Create speech with a character voice |
266
+ | `everypixel audio tts-create` | Create speech with a Qwen preset or saved ElevenLabs voice |
267
+ | `everypixel audio tts-clone` | Create Qwen speech from an audio sample or saved voice |
268
+ | `everypixel audio tts-design` | Design voice previews from a description |
269
+ | `everypixel audio tts-voice` | Save audio samples or a design preview as a voice |
270
+ | `everypixel audio tts-voices` | List presets and saved voices |
271
+ | `everypixel audio tts-delete` | Delete an owned saved voice |
260
272
 
261
273
  Lipsync supports 720p; the API checks source media dimensions and duration.
262
274
 
@@ -281,6 +293,56 @@ everypixel audio tts-create \
281
293
  --download ./outputs
282
294
  ```
283
295
 
296
+ `tts-create` lets the API choose the model when `--model` is omitted.
297
+ `tts-clone` and `tts-design` default to `--model qwen3`.
298
+ `--speaker`, `--style`, `--language`, and `--seed` are Qwen controls; `--character` applies only to Qwen voice design.
299
+ Qwen text is limited to 200 characters. ElevenLabs synthesis accepts up to
300
+ 2048 characters and requires `--voice-id`. Supported synthesis models are
301
+ `eleven_v4`, `eleven_v4_turbo`, `eleven_v3`, `eleven_v3_conversational`,
302
+ `eleven_multilingual_v2`, and `eleven_flash_v2_5`. ElevenLabs design uses
303
+ `eleven_multilingual_ttv_v2` or `eleven_ttv_v3`, with 100–1000 characters of
304
+ text and a 20–1000 character `--prompt`.
305
+
306
+ Save a returned preview with `tts-voice --name "Narrator" --preview-id UUID`, or save local
307
+ samples with `tts-voice --name "My voice" --provider elevenlabs --audio ./sample.wav`.
308
+ `--audio` is repeatable for ElevenLabs; Qwen (`--provider labs`, the default)
309
+ requires one sample. Saving returns a voice card immediately. Use its ID with
310
+ `tts-create --model eleven_v3 --voice-id UUID --text "Hello"` or
311
+ `tts-clone --voice-id UUID --text "Hello"`. List voices with
312
+ `tts-voices --provider elevenlabs --offset 0 --limit 50`, and delete an owned
313
+ voice with `tts-delete --voice-id UUID`.
314
+
315
+ ### Chat
316
+
317
+ ```bash
318
+ everypixel chat "Explain how a rainbow forms."
319
+ everypixel chat "Review this idea." --system "Be concise." --stream
320
+ everypixel chat "Solve this problem." --reasoning-effort high --show-reasoning
321
+ everypixel chat --input-file ./conversation.json --output-json
322
+ ```
323
+
324
+ Chat uses the same credentials as other commands and requires the client's
325
+ `chat` scope. The default model is `glm-5.3`. Each invocation sends one request;
326
+ use `--input-file` for conversation history, function tools, or other API options:
327
+
328
+ ```json
329
+ {
330
+ "messages": [
331
+ {"role": "user", "content": "What causes rainbows?"},
332
+ {"role": "assistant", "content": "Sunlight refracts and reflects in water droplets."},
333
+ {"role": "user", "content": "Why do the colors separate?"}
334
+ ],
335
+ "max_completion_tokens": 4096
336
+ }
337
+ ```
338
+
339
+ A positional prompt appends a user message to the supplied history. CLI options
340
+ override matching fields in the file. `--stream` displays text as it arrives;
341
+ JSON output returns one assembled response, including reasoning, tool calls,
342
+ and usage. Function calls are returned for the caller to handle. The MCP `chat`
343
+ tool accepts the same structured request and returns a complete response.
344
+ Use `--request-timeout` to change the chat HTTP timeout (default: 240 seconds).
345
+
284
346
  ### Visual Analysis
285
347
 
286
348
  Analysis commands accept HTTP(S) URLs and local files. Local files are uploaded
@@ -1,8 +1,8 @@
1
1
  # Everypixel CLI
2
2
 
3
3
  Official command-line interface for the [Everypixel API](https://labs.everypixel.com/).
4
- Generate and edit images and videos, work with audio, run visual analysis, and
5
- automate Everypixel workflows from a terminal or an MCP client.
4
+ Generate and edit images and videos, chat with GLM-5.3, work with audio, run
5
+ visual analysis, and automate Everypixel workflows from a terminal or an MCP client.
6
6
 
7
7
  The CLI is designed for both people and agents: commands use readable terminal
8
8
  output by default and can return stable JSON for scripts and tool integrations.
@@ -147,6 +147,15 @@ to select their aspect ratio. Raster models also accept `--resolution`.
147
147
 
148
148
  `--lora-url` is supported only by `zimage`.
149
149
 
150
+ GPT Image models are `gpt-image-2` and `gpt-image-2.5-sunburst`. Both generation
151
+ and editing accept `--quality low|medium|high` (default: `medium`); Sunburst also
152
+ supports `xhigh` and `max`. Use `--size square` with `--resolution 3k`.
153
+
154
+ ```bash
155
+ everypixel image generate "a detailed botanical illustration" \
156
+ --model gpt-image-2.5-sunburst --quality high --download ./outputs
157
+ ```
158
+
150
159
  Grok image models are `grok-imagine`, `grok-imagine-2`, and
151
160
  `grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
152
161
  `wan22` have been removed from the API.
@@ -220,9 +229,12 @@ They support 4K output. The default `seedvr2` supports up to 1440p, with a
220
229
  | `everypixel lipsync video` | Synchronize a video with an audio track |
221
230
  | `everypixel lipsync image` | Create a lipsync video from a still image and audio |
222
231
  | `everypixel audio transcribe` | Transcribe speech from an audio file |
223
- | `everypixel audio tts-create` | Create speech with a built-in speaker |
224
- | `everypixel audio tts-clone` | Create speech from a cloned voice sample |
225
- | `everypixel audio tts-voice` | Create speech with a character voice |
232
+ | `everypixel audio tts-create` | Create speech with a Qwen preset or saved ElevenLabs voice |
233
+ | `everypixel audio tts-clone` | Create Qwen speech from an audio sample or saved voice |
234
+ | `everypixel audio tts-design` | Design voice previews from a description |
235
+ | `everypixel audio tts-voice` | Save audio samples or a design preview as a voice |
236
+ | `everypixel audio tts-voices` | List presets and saved voices |
237
+ | `everypixel audio tts-delete` | Delete an owned saved voice |
226
238
 
227
239
  Lipsync supports 720p; the API checks source media dimensions and duration.
228
240
 
@@ -247,6 +259,56 @@ everypixel audio tts-create \
247
259
  --download ./outputs
248
260
  ```
249
261
 
262
+ `tts-create` lets the API choose the model when `--model` is omitted.
263
+ `tts-clone` and `tts-design` default to `--model qwen3`.
264
+ `--speaker`, `--style`, `--language`, and `--seed` are Qwen controls; `--character` applies only to Qwen voice design.
265
+ Qwen text is limited to 200 characters. ElevenLabs synthesis accepts up to
266
+ 2048 characters and requires `--voice-id`. Supported synthesis models are
267
+ `eleven_v4`, `eleven_v4_turbo`, `eleven_v3`, `eleven_v3_conversational`,
268
+ `eleven_multilingual_v2`, and `eleven_flash_v2_5`. ElevenLabs design uses
269
+ `eleven_multilingual_ttv_v2` or `eleven_ttv_v3`, with 100–1000 characters of
270
+ text and a 20–1000 character `--prompt`.
271
+
272
+ Save a returned preview with `tts-voice --name "Narrator" --preview-id UUID`, or save local
273
+ samples with `tts-voice --name "My voice" --provider elevenlabs --audio ./sample.wav`.
274
+ `--audio` is repeatable for ElevenLabs; Qwen (`--provider labs`, the default)
275
+ requires one sample. Saving returns a voice card immediately. Use its ID with
276
+ `tts-create --model eleven_v3 --voice-id UUID --text "Hello"` or
277
+ `tts-clone --voice-id UUID --text "Hello"`. List voices with
278
+ `tts-voices --provider elevenlabs --offset 0 --limit 50`, and delete an owned
279
+ voice with `tts-delete --voice-id UUID`.
280
+
281
+ ### Chat
282
+
283
+ ```bash
284
+ everypixel chat "Explain how a rainbow forms."
285
+ everypixel chat "Review this idea." --system "Be concise." --stream
286
+ everypixel chat "Solve this problem." --reasoning-effort high --show-reasoning
287
+ everypixel chat --input-file ./conversation.json --output-json
288
+ ```
289
+
290
+ Chat uses the same credentials as other commands and requires the client's
291
+ `chat` scope. The default model is `glm-5.3`. Each invocation sends one request;
292
+ use `--input-file` for conversation history, function tools, or other API options:
293
+
294
+ ```json
295
+ {
296
+ "messages": [
297
+ {"role": "user", "content": "What causes rainbows?"},
298
+ {"role": "assistant", "content": "Sunlight refracts and reflects in water droplets."},
299
+ {"role": "user", "content": "Why do the colors separate?"}
300
+ ],
301
+ "max_completion_tokens": 4096
302
+ }
303
+ ```
304
+
305
+ A positional prompt appends a user message to the supplied history. CLI options
306
+ override matching fields in the file. `--stream` displays text as it arrives;
307
+ JSON output returns one assembled response, including reasoning, tool calls,
308
+ and usage. Function calls are returned for the caller to handle. The MCP `chat`
309
+ tool accepts the same structured request and returns a complete response.
310
+ Use `--request-timeout` to change the chat HTTP timeout (default: 240 seconds).
311
+
250
312
  ### Visual Analysis
251
313
 
252
314
  Analysis commands accept HTTP(S) URLs and local files. Local files are uploaded
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -0,0 +1,192 @@
1
+ """Chat request construction and completion assembly without presentation code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Callable, Iterator
7
+ from typing import Any, Protocol
8
+
9
+ from ..errors import APIResponseError, ValidationCLIError
10
+ from ..schemas import ChatRequest
11
+ from .models import ExecutionOptions, OperationResult
12
+
13
+
14
+ class ChatClientProtocol(Protocol):
15
+ def request(
16
+ self,
17
+ method: str,
18
+ path: str,
19
+ *,
20
+ json: dict[str, Any] | None = None,
21
+ params: dict[str, Any] | None = None,
22
+ files: dict[str, Any] | None = None,
23
+ auth_required: bool = True,
24
+ timeout: float | None = None,
25
+ ) -> Any: ...
26
+
27
+ def stream_request(
28
+ self, method: str, path: str, *, json: dict[str, Any], timeout: float
29
+ ) -> Iterator[dict[str, Any]]: ...
30
+
31
+
32
+ def build_chat_payload(
33
+ *,
34
+ payload: dict[str, Any],
35
+ prompt: str | None,
36
+ system: str | None,
37
+ options: dict[str, Any],
38
+ ) -> dict[str, Any]:
39
+ values = {
40
+ **payload,
41
+ **{key: value for key, value in options.items() if value is not None},
42
+ }
43
+ values.setdefault("model", "glm-5.3")
44
+ messages = values.get("messages", [])
45
+ if not isinstance(messages, list):
46
+ raise ValidationCLIError("messages must be an array")
47
+ messages = list(messages)
48
+ if system is not None:
49
+ messages.insert(0, {"role": "system", "content": system})
50
+ if prompt is not None:
51
+ messages.append({"role": "user", "content": prompt})
52
+ values["messages"] = messages
53
+ if values.get("stream") and "stream_options" not in values:
54
+ values["stream_options"] = {"include_usage": True}
55
+ return ChatRequest.model_validate(values).model_dump(mode="json", exclude_none=True)
56
+
57
+
58
+ class ChatCompletion:
59
+ """Combine text, reasoning, and tool-call fragments into a completion."""
60
+
61
+ def __init__(self) -> None:
62
+ self.metadata: dict[str, Any] = {}
63
+ self.choices: dict[int, dict[str, Any]] = {}
64
+ self.tools: dict[int, dict[int, dict[str, Any]]] = {}
65
+
66
+ def add(self, event: dict[str, Any]) -> None:
67
+ choices = event.get("choices")
68
+ if not isinstance(choices, list):
69
+ raise APIResponseError("API returned invalid chat choices")
70
+ self.metadata.update(
71
+ {key: value for key, value in event.items() if key != "choices"}
72
+ )
73
+ for item in choices:
74
+ if not isinstance(item, dict):
75
+ raise APIResponseError("API returned an invalid chat choice")
76
+ index = item.get("index")
77
+ delta = item.get("delta")
78
+ if type(index) is not int or index < 0 or not isinstance(delta, dict):
79
+ raise APIResponseError("API returned an invalid chat delta")
80
+ choice = self.choices.setdefault(
81
+ index,
82
+ {
83
+ "index": index,
84
+ "message": {"role": "assistant", "content": None},
85
+ "finish_reason": None,
86
+ },
87
+ )
88
+ choice.update(
89
+ {
90
+ key: value
91
+ for key, value in item.items()
92
+ if key not in {"delta", "finish_reason"}
93
+ }
94
+ )
95
+ if item.get("finish_reason") is not None:
96
+ choice["finish_reason"] = item["finish_reason"]
97
+ message = choice["message"]
98
+ for key in ("content", "reasoning_content", "refusal"):
99
+ append_fragment(message, delta, key)
100
+ if "role" in delta:
101
+ message["role"] = delta["role"]
102
+ tool_calls = delta.get("tool_calls")
103
+ if tool_calls is not None:
104
+ self._add_tools(index, tool_calls)
105
+
106
+ def _add_tools(self, choice_index: int, calls: Any) -> None:
107
+ if not isinstance(calls, list):
108
+ raise APIResponseError("API returned invalid tool calls")
109
+ tools = self.tools.setdefault(choice_index, {})
110
+ for call in calls:
111
+ if not isinstance(call, dict):
112
+ raise APIResponseError("API returned an invalid tool call")
113
+ index = call.get("index")
114
+ if type(index) is not int or index < 0:
115
+ raise APIResponseError("API returned an invalid tool call index")
116
+ tool = tools.setdefault(index, {"function": {"name": "", "arguments": ""}})
117
+ append_fragment(tool, call, "id")
118
+ if "type" in call:
119
+ tool["type"] = call["type"]
120
+ function = call.get("function")
121
+ if function is not None:
122
+ if not isinstance(function, dict):
123
+ raise APIResponseError("API returned an invalid function call")
124
+ for key in ("name", "arguments"):
125
+ append_fragment(tool["function"], function, key)
126
+
127
+ def result(self) -> dict[str, Any]:
128
+ if not self.choices or any(
129
+ choice["finish_reason"] is None for choice in self.choices.values()
130
+ ):
131
+ raise APIResponseError("API returned an unfinished chat completion")
132
+ for index, tools in self.tools.items():
133
+ self.choices[index]["message"]["tool_calls"] = [
134
+ tools[key] for key in sorted(tools)
135
+ ]
136
+ return {
137
+ **self.metadata,
138
+ "object": "chat.completion",
139
+ "choices": [self.choices[index] for index in sorted(self.choices)],
140
+ }
141
+
142
+
143
+ def append_fragment(target: dict[str, Any], delta: dict[str, Any], key: str) -> None:
144
+ value = delta.get(key)
145
+ if value is None:
146
+ return
147
+ if not isinstance(value, str):
148
+ raise APIResponseError("API returned an invalid chat text fragment")
149
+ target[key] = (target.get(key) or "") + value
150
+
151
+
152
+ class ChatService:
153
+ def __init__(self, client: ChatClientProtocol) -> None:
154
+ self.client = client
155
+
156
+ def execute(
157
+ self,
158
+ payload: dict[str, Any],
159
+ *,
160
+ request_timeout: float = 240.0,
161
+ execution: ExecutionOptions | None = None,
162
+ on_chunk: Callable[[dict[str, Any]], None] | None = None,
163
+ ) -> OperationResult:
164
+ if not math.isfinite(request_timeout) or request_timeout <= 0:
165
+ raise ValidationCLIError("request timeout must be a finite positive number")
166
+ execution = execution or ExecutionOptions()
167
+ execution.check_cancelled()
168
+ if not payload.get("stream"):
169
+ value = self.client.request(
170
+ "POST", "/v1/chat/completions", json=payload, timeout=request_timeout
171
+ )
172
+ execution.check_cancelled()
173
+ return OperationResult(value=value)
174
+ completion = ChatCompletion()
175
+ events = self.client.stream_request(
176
+ "POST", "/v1/chat/completions", json=payload, timeout=request_timeout
177
+ )
178
+ # Closing the generator also closes its HTTP response on cancellation
179
+ # or a rendering failure, without starting another generation.
180
+ try:
181
+ for event in events:
182
+ execution.check_cancelled()
183
+ completion.add(event)
184
+ if on_chunk is not None:
185
+ on_chunk(event)
186
+ execution.check_cancelled()
187
+ finally:
188
+ close = getattr(events, "close", None)
189
+ if close is not None:
190
+ close()
191
+ execution.check_cancelled()
192
+ return OperationResult(value=completion.result())