ChatImg 0.1.0__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.
chatimg/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """ChatImg package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
chatimg/cli.py ADDED
@@ -0,0 +1,9 @@
1
+ """CLI entrypoint for ChatImg."""
2
+
3
+ from chatimg.image.cli import main
4
+
5
+ __all__ = ["main"]
6
+
7
+
8
+ if __name__ == "__main__":
9
+ main()
chatimg/config.py ADDED
@@ -0,0 +1,138 @@
1
+ """Typed environment configuration for ChatImg."""
2
+
3
+ from chatenv import BaseEnvConfig, EnvField
4
+
5
+
6
+ class ChatImgConfig(BaseEnvConfig):
7
+ """ChatImg ChatEnv configuration for image generation providers."""
8
+
9
+ _title = "ChatImg Configuration"
10
+ _aliases = ["chatimg", "image"]
11
+ _storage_dir = "ChatImg"
12
+
13
+ DASHSCOPE_API_KEY = EnvField(
14
+ "DASHSCOPE_API_KEY",
15
+ desc="Aliyun DashScope API key for Tongyi Wanxiang.",
16
+ is_sensitive=True,
17
+ )
18
+ HUGGINGFACE_HUB_TOKEN = EnvField(
19
+ "HUGGINGFACE_HUB_TOKEN",
20
+ desc="Hugging Face User Access Token.",
21
+ is_sensitive=True,
22
+ )
23
+ LIBLIB_MODEL_ID = EnvField(
24
+ "LIBLIB_MODEL_ID",
25
+ desc="Default LiblibAI model ID.",
26
+ )
27
+ LIBLIB_ACCESS_KEY = EnvField(
28
+ "LIBLIB_ACCESS_KEY",
29
+ desc="LiblibAI access key.",
30
+ is_sensitive=True,
31
+ )
32
+ LIBLIB_SECRET_KEY = EnvField(
33
+ "LIBLIB_SECRET_KEY",
34
+ desc="LiblibAI secret key.",
35
+ is_sensitive=True,
36
+ )
37
+ POLLINATIONS_API_KEY = EnvField(
38
+ "POLLINATIONS_API_KEY",
39
+ desc="Pollinations API key from enter.pollinations.ai.",
40
+ is_sensitive=True,
41
+ )
42
+ POLLINATIONS_MODEL_ID = EnvField(
43
+ "POLLINATIONS_MODEL_ID",
44
+ default="flux",
45
+ desc="Default Pollinations image model ID.",
46
+ )
47
+ SILICONFLOW_API_KEY = EnvField(
48
+ "SILICONFLOW_API_KEY",
49
+ desc="SiliconFlow API key.",
50
+ is_sensitive=True,
51
+ )
52
+ SILICONFLOW_MODEL_ID = EnvField(
53
+ "SILICONFLOW_MODEL_ID",
54
+ default="black-forest-labs/FLUX.1-schnell",
55
+ desc="Default SiliconFlow image model ID.",
56
+ )
57
+ OPENAI_ACCESS_TOKEN = EnvField(
58
+ "OPENAI_ACCESS_TOKEN",
59
+ desc="OpenAI/ChatGPT OAuth access token for Codex image generation.",
60
+ is_sensitive=True,
61
+ )
62
+ OPENAI_CODEX_ACCESS_TOKEN = EnvField(
63
+ "OPENAI_CODEX_ACCESS_TOKEN",
64
+ desc="Legacy Codex image OAuth access token alias.",
65
+ is_sensitive=True,
66
+ )
67
+ OPENAI_REFRESH_TOKEN = EnvField(
68
+ "OPENAI_REFRESH_TOKEN",
69
+ desc="OpenAI/ChatGPT OAuth refresh token for Codex image generation.",
70
+ is_sensitive=True,
71
+ )
72
+ OPENAI_CODEX_AUTH_JSON = EnvField(
73
+ "OPENAI_CODEX_AUTH_JSON",
74
+ default="~/.hermes/auth.json",
75
+ desc="Hermes auth.json path used as a Codex OAuth token fallback.",
76
+ )
77
+ OPENAI_OAUTH_BASE_URL = EnvField(
78
+ "OPENAI_OAUTH_BASE_URL",
79
+ default="https://auth.openai.com",
80
+ desc="OpenAI OAuth auth server base URL.",
81
+ )
82
+ OPENAI_ACCESS_TOKEN_EXPIRES_AT = EnvField(
83
+ "OPENAI_ACCESS_TOKEN_EXPIRES_AT",
84
+ desc="UTC ISO timestamp for OPENAI_ACCESS_TOKEN expiry.",
85
+ )
86
+ OPENAI_IMAGE_MODEL = EnvField(
87
+ "OPENAI_IMAGE_MODEL",
88
+ default="gpt-image-2-medium",
89
+ desc="Default Codex/OpenAI image model preset.",
90
+ )
91
+ OPENAI_IMAGE_ASPECT_RATIO = EnvField(
92
+ "OPENAI_IMAGE_ASPECT_RATIO",
93
+ default="square",
94
+ desc="Default Codex image aspect ratio.",
95
+ )
96
+ OPENAI_CODEX_HOST_MODEL = EnvField(
97
+ "OPENAI_CODEX_HOST_MODEL",
98
+ default="gpt-5.4",
99
+ desc="Codex host model used to invoke the image_generation tool.",
100
+ )
101
+ OPENAI_CODEX_BASE_URL = EnvField(
102
+ "OPENAI_CODEX_BASE_URL",
103
+ default="https://chatgpt.com/backend-api/codex",
104
+ desc="Codex Responses API base URL.",
105
+ )
106
+ OPENAI_CODEX_TIMEOUT = EnvField(
107
+ "OPENAI_CODEX_TIMEOUT",
108
+ default="300",
109
+ desc="Codex image request timeout in seconds.",
110
+ )
111
+
112
+ @classmethod
113
+ def test(cls) -> None:
114
+ """Validate schema registration without external side effects."""
115
+
116
+ print(f"Testing {cls._title}...")
117
+ print("Schema loaded; provider network checks are command-specific.")
118
+
119
+
120
+ # Backward-compatible aliases for code migrated from ChatTool provider modules.
121
+ ChatimgConfig = ChatImgConfig
122
+ TongyiConfig = ChatImgConfig
123
+ HuggingFaceConfig = ChatImgConfig
124
+ LiblibConfig = ChatImgConfig
125
+ PollinationsConfig = ChatImgConfig
126
+ SiliconFlowConfig = ChatImgConfig
127
+ OpenAIConfig = ChatImgConfig
128
+
129
+ __all__ = [
130
+ "ChatImgConfig",
131
+ "ChatimgConfig",
132
+ "TongyiConfig",
133
+ "HuggingFaceConfig",
134
+ "LiblibConfig",
135
+ "PollinationsConfig",
136
+ "SiliconFlowConfig",
137
+ "OpenAIConfig",
138
+ ]
@@ -0,0 +1,45 @@
1
+ from .base import ImageGenerator
2
+ from .tongyi import TongyiImageGenerator
3
+ from .huggingface import HuggingFaceImageGenerator
4
+ from .liblib import LiblibImageGenerator
5
+ from .pollinations import PollinationsImageGenerator
6
+ from .siliconflow import SiliconFlowImageGenerator
7
+ from .codex import CodexImageGenerator
8
+
9
+ __all__ = [
10
+ "ImageGenerator",
11
+ "TongyiImageGenerator",
12
+ "HuggingFaceImageGenerator",
13
+ "LiblibImageGenerator",
14
+ "PollinationsImageGenerator",
15
+ "SiliconFlowImageGenerator",
16
+ "CodexImageGenerator",
17
+ ]
18
+
19
+ def create_generator(provider: str, **kwargs) -> ImageGenerator:
20
+ """
21
+ Factory function to create an image generator instance.
22
+
23
+ Args:
24
+ provider (str): 'tongyi', 'huggingface', 'liblib', 'pollinations',
25
+ 'siliconflow', 'codex', or 'openai-codex'
26
+ **kwargs: Additional config (api_key, etc.)
27
+
28
+ Returns:
29
+ ImageGenerator: The initialized generator.
30
+ """
31
+ if provider == "tongyi":
32
+ return TongyiImageGenerator(**kwargs)
33
+ elif provider == "huggingface":
34
+ return HuggingFaceImageGenerator(**kwargs)
35
+ elif provider == "liblib":
36
+ return LiblibImageGenerator(**kwargs)
37
+ elif provider == "pollinations":
38
+ return PollinationsImageGenerator(**kwargs)
39
+ elif provider == "siliconflow":
40
+ return SiliconFlowImageGenerator(**kwargs)
41
+ elif provider in {"codex", "openai-codex"}:
42
+ return CodexImageGenerator(**kwargs)
43
+
44
+ else:
45
+ raise ValueError(f"Unknown provider: {provider}")
chatimg/image/base.py ADDED
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional, Any
3
+
4
+ class ImageGenerator(ABC):
5
+ """
6
+ Abstract base class for all image generation tools.
7
+ """
8
+
9
+ @abstractmethod
10
+ def generate(self, prompt: str, **kwargs) -> Any:
11
+ """
12
+ Generate an image from the given prompt.
13
+
14
+ Args:
15
+ prompt (str): The text description for the image.
16
+ **kwargs: Additional parameters specific to the implementation (e.g. style, size).
17
+
18
+ Returns:
19
+ Any: The result object (e.g. image URL, bytes, or file path).
20
+ """
21
+ pass
22
+
23
+ def get_models(self) -> list:
24
+ """
25
+ Get available models for image generation.
26
+
27
+ Returns:
28
+ list: A list of available model IDs or objects.
29
+ """
30
+ return []
chatimg/image/cli.py ADDED
@@ -0,0 +1,422 @@
1
+ from chatimg import __version__
2
+ import click
3
+ from chatstyle import (
4
+ CommandField,
5
+ CommandSchema,
6
+ add_interactive_option,
7
+ resolve_command_inputs,
8
+ )
9
+ from chatimg.image import create_generator
10
+ from chatimg.image.helpers import (
11
+ download_binary,
12
+ echo_model_list,
13
+ resolve_generated_output_path,
14
+ save_binary,
15
+ )
16
+
17
+
18
+ PROMPT_SCHEMA = CommandSchema(
19
+ name="image-prompt",
20
+ fields=(CommandField("prompt", prompt="prompt", required=True),),
21
+ )
22
+
23
+
24
+ HF_GENERATE_SCHEMA = CommandSchema(
25
+ name="image-huggingface-generate",
26
+ fields=(
27
+ CommandField("prompt", prompt="prompt", required=True),
28
+ ),
29
+ )
30
+
31
+
32
+ @click.group()
33
+ @click.version_option(__version__, prog_name="chatimg")
34
+ def main() -> None:
35
+ """ChatImg image generation tools."""
36
+ pass
37
+
38
+
39
+ @main.group()
40
+ def liblib():
41
+ """LiblibAI tools."""
42
+ pass
43
+
44
+
45
+ @liblib.command(name="generate")
46
+ @click.argument("prompt", required=False)
47
+ @click.option("--model-id", help="Model ID for generation (required).")
48
+ @click.option("--output", "-o", help="Optional output file path to download the image.")
49
+ @add_interactive_option
50
+ def liblib_generate(prompt, model_id, output, interactive):
51
+ """Generate an image using LiblibAI."""
52
+ inputs = resolve_command_inputs(
53
+ schema=PROMPT_SCHEMA,
54
+ provided={"prompt": prompt},
55
+ interactive=interactive,
56
+ usage="Usage: chatimg liblib generate [PROMPT] [-i|-I]",
57
+ )
58
+ prompt = inputs["prompt"]
59
+
60
+ try:
61
+ generator = create_generator("liblib")
62
+ click.echo("Generating image with LiblibAI...")
63
+
64
+ kwargs = {}
65
+ if model_id:
66
+ kwargs["model_id"] = model_id
67
+
68
+ result = generator.generate(prompt, **kwargs)
69
+ click.echo(f"Image URL: {result}")
70
+
71
+ if output and result.startswith("http"):
72
+ download_binary(result, output)
73
+
74
+ except Exception as e:
75
+ raise click.ClickException(str(e)) from e
76
+
77
+
78
+ @liblib.command(name="list-models")
79
+ def liblib_list_models():
80
+ """List available models for LiblibAI."""
81
+ try:
82
+ generator = create_generator("liblib")
83
+ models = generator.get_models()
84
+ click.echo("Available models for LiblibAI:")
85
+ for model in models:
86
+ click.echo(
87
+ f"- {model.get('name', 'Unknown')} (ID: {model.get('id', 'Unknown')})"
88
+ )
89
+ except Exception as e:
90
+ raise click.ClickException(str(e)) from e
91
+
92
+
93
+ @main.group()
94
+ def huggingface():
95
+ """Hugging Face tools."""
96
+ pass
97
+
98
+
99
+ @huggingface.command(name="generate")
100
+ @click.argument("prompt", required=False)
101
+ @click.option(
102
+ "--output",
103
+ "-o",
104
+ required=False,
105
+ help="Optional output file path. Defaults to ./generated/image_huggingface_<model>_<timestamp>.png for bytes results.",
106
+ )
107
+ @add_interactive_option
108
+ def huggingface_generate(prompt, output, interactive):
109
+ """Generate an image using Hugging Face."""
110
+ inputs = resolve_command_inputs(
111
+ schema=HF_GENERATE_SCHEMA,
112
+ provided={"prompt": prompt},
113
+ interactive=interactive,
114
+ usage="Usage: chatimg huggingface generate [PROMPT] [-o PATH] [-i|-I]",
115
+ )
116
+ prompt = inputs["prompt"]
117
+
118
+ try:
119
+ from chatimg.image.huggingface import HuggingFaceImageGenerator
120
+
121
+ generator = create_generator("huggingface")
122
+ click.echo("Generating image with Hugging Face...")
123
+ result = generator.generate(prompt)
124
+
125
+ if isinstance(result, bytes):
126
+ output_path = resolve_generated_output_path(
127
+ output,
128
+ provider="huggingface",
129
+ model=HuggingFaceImageGenerator.DEFAULT_MODEL,
130
+ )
131
+ save_binary(result, output_path)
132
+ click.echo(f"Image saved to {output_path}")
133
+ else:
134
+ click.echo(f"Result: {result}")
135
+ except Exception as e:
136
+ raise click.ClickException(str(e)) from e
137
+
138
+
139
+ @main.group()
140
+ def tongyi():
141
+ """Tongyi Wanxiang tools."""
142
+ pass
143
+
144
+
145
+ @tongyi.command(name="generate")
146
+ @click.argument("prompt", required=False)
147
+ @click.option("--style", default="<auto>", help="Image style.")
148
+ @click.option("--size", default="1024*1024", help="Image size.")
149
+ @click.option("--output", "-o", help="Optional output file path to download the image.")
150
+ @add_interactive_option
151
+ def tongyi_generate(prompt, style, size, output, interactive):
152
+ """Generate an image using Tongyi Wanxiang."""
153
+ inputs = resolve_command_inputs(
154
+ schema=PROMPT_SCHEMA,
155
+ provided={"prompt": prompt},
156
+ interactive=interactive,
157
+ usage="Usage: chatimg tongyi generate [PROMPT] [-i|-I]",
158
+ )
159
+ prompt = inputs["prompt"]
160
+
161
+ try:
162
+ generator = create_generator("tongyi")
163
+ click.echo("Generating image with Tongyi Wanxiang...")
164
+ result = generator.generate(prompt, style=style, size=size)
165
+
166
+ # Tongyi returns a list of dicts with 'url'
167
+ if isinstance(result, list):
168
+ for i, item in enumerate(result):
169
+ url = item.get("url")
170
+ click.echo(f"Image {i + 1}: {url}")
171
+ if output and url:
172
+ download_binary(url, output)
173
+ else:
174
+ click.echo(f"Result: {result}")
175
+
176
+ except Exception as e:
177
+ raise click.ClickException(str(e)) from e
178
+
179
+
180
+ @main.group()
181
+ def pollinations():
182
+ """Pollinations.ai tools."""
183
+ pass
184
+
185
+
186
+ @pollinations.command(name="generate")
187
+ @click.argument("prompt", required=False)
188
+ @click.option("--model", help="Model name (flux, turbo, etc).")
189
+ @click.option("--width", default=1024, help="Image width.")
190
+ @click.option("--height", default=1024, help="Image height.")
191
+ @click.option("--output", "-o", help="Optional output file path to download the image.")
192
+ @add_interactive_option
193
+ def pollinations_generate(prompt, model, width, height, output, interactive):
194
+ """Generate an image using Pollinations.ai."""
195
+ inputs = resolve_command_inputs(
196
+ schema=PROMPT_SCHEMA,
197
+ provided={"prompt": prompt},
198
+ interactive=interactive,
199
+ usage="Usage: chatimg pollinations generate [PROMPT] [-i|-I]",
200
+ )
201
+ prompt = inputs["prompt"]
202
+
203
+ try:
204
+ from chatimg.config import PollinationsConfig
205
+ from chatimg.image import create_generator
206
+
207
+ model = model or PollinationsConfig.POLLINATIONS_MODEL_ID.value or "flux"
208
+ generator = create_generator("pollinations", model=model)
209
+ click.echo(f"Generating image with Pollinations.ai (model: {model})...")
210
+ result = generator.generate(prompt, width=width, height=height)
211
+
212
+ for i, url in enumerate(result):
213
+ click.echo(f"Image URL: {url}")
214
+
215
+ if output:
216
+ click.echo(f"Downloading to {output}...")
217
+ headers = {
218
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
219
+ }
220
+ if PollinationsConfig.POLLINATIONS_API_KEY.value:
221
+ headers["Authorization"] = (
222
+ f"Bearer {PollinationsConfig.POLLINATIONS_API_KEY.value}"
223
+ )
224
+
225
+ download_binary(url, output, headers=headers)
226
+
227
+ except Exception as e:
228
+ raise click.ClickException(str(e)) from e
229
+
230
+
231
+ @pollinations.command(name="list-models")
232
+ def pollinations_list_models():
233
+ """List available image models for Pollinations.ai."""
234
+ try:
235
+ from chatimg.image import create_generator
236
+
237
+ generator = create_generator("pollinations")
238
+ models = generator.get_models()
239
+ echo_model_list(models, "Available image models for Pollinations.ai:")
240
+ except Exception as e:
241
+ raise click.ClickException(str(e)) from e
242
+
243
+
244
+ @main.group()
245
+ def siliconflow():
246
+ """SiliconFlow tools."""
247
+ pass
248
+
249
+
250
+ @main.group()
251
+ def codex():
252
+ """ChatGPT/Codex OAuth image tools."""
253
+ pass
254
+
255
+
256
+ @siliconflow.command(name="generate")
257
+ @click.argument("prompt", required=False)
258
+ @click.option("--model", help="Model name.")
259
+ @click.option("--size", default="1024x1024", help="Image size (e.g., 1024x1024).")
260
+ @click.option("--output", "-o", help="Optional output file path to download the image.")
261
+ @add_interactive_option
262
+ def siliconflow_generate(prompt, model, size, output, interactive):
263
+ """Generate an image using SiliconFlow API."""
264
+ inputs = resolve_command_inputs(
265
+ schema=PROMPT_SCHEMA,
266
+ provided={"prompt": prompt},
267
+ interactive=interactive,
268
+ usage="Usage: chatimg siliconflow generate [PROMPT] [-i|-I]",
269
+ )
270
+ prompt = inputs["prompt"]
271
+
272
+ try:
273
+ from chatimg.config import SiliconFlowConfig
274
+ from chatimg.image import create_generator
275
+
276
+ # Determine model
277
+ model = (
278
+ model
279
+ or SiliconFlowConfig.SILICONFLOW_MODEL_ID.value
280
+ or "black-forest-labs/FLUX.1-schnell"
281
+ )
282
+
283
+ generator = create_generator("siliconflow")
284
+ click.echo(f"Generating image with SiliconFlow (model: {model})...")
285
+
286
+ result = generator.generate(prompt, model=model, size=size)
287
+
288
+ for i, url in enumerate(result):
289
+ click.echo(f"Image URL: {url}")
290
+
291
+ if output:
292
+ click.echo(f"Downloading to {output}...")
293
+ download_binary(url, output)
294
+
295
+ except Exception as e:
296
+ raise click.ClickException(str(e)) from e
297
+
298
+
299
+ @siliconflow.command(name="list-models")
300
+ def siliconflow_list_models():
301
+ """List available image models for SiliconFlow."""
302
+ try:
303
+ from chatimg.image import create_generator
304
+
305
+ generator = create_generator("siliconflow")
306
+ models = generator.get_models()
307
+
308
+ # Filter for text-to-image models only if possible
309
+ image_models = []
310
+ for m in models:
311
+ # Check type or sub_type
312
+ is_image = m.get("type") == "image" or m.get("sub_type") == "text-to-image"
313
+ if is_image:
314
+ image_models.append(m)
315
+
316
+ if not image_models:
317
+ image_models = models
318
+
319
+ click.echo("Available image models for SiliconFlow:")
320
+ for model in image_models:
321
+ model_id = model.get("id")
322
+ # Check if it's a free model (no Pro/ prefix)
323
+ is_free = not model_id.startswith("Pro/")
324
+ price_info = "FREE" if is_free else "PAID"
325
+
326
+ click.echo(f"- {model_id} [{price_info}]")
327
+
328
+ except Exception as e:
329
+ raise click.ClickException(str(e)) from e
330
+
331
+
332
+ @codex.command(name="generate")
333
+ @click.argument("prompt", required=False)
334
+ @click.option(
335
+ "--aspect-ratio",
336
+ type=click.Choice(["square", "landscape", "portrait"]),
337
+ help="Image aspect ratio.",
338
+ )
339
+ @click.option(
340
+ "--image-model",
341
+ type=click.Choice(
342
+ ["gpt-image-2-low", "gpt-image-2-medium", "gpt-image-2-high"]
343
+ ),
344
+ help="Codex image model preset.",
345
+ )
346
+ @click.option(
347
+ "--host-model",
348
+ help="Codex host model used to invoke the image_generation tool.",
349
+ )
350
+ @click.option(
351
+ "--base-url",
352
+ help="Override the Codex backend base URL. The OAuth token is sent to this host.",
353
+ )
354
+ @click.option(
355
+ "--timeout",
356
+ type=float,
357
+ help="Request timeout in seconds.",
358
+ )
359
+ @click.option(
360
+ "--output",
361
+ "-o",
362
+ help="Optional output file path. Defaults to ./generated/image_codex_<model>_<timestamp>.png",
363
+ )
364
+ @add_interactive_option
365
+ def codex_generate(
366
+ prompt,
367
+ aspect_ratio,
368
+ image_model,
369
+ host_model,
370
+ base_url,
371
+ timeout,
372
+ output,
373
+ interactive,
374
+ ):
375
+ """Generate an image using the ChatGPT/Codex OAuth image bridge."""
376
+ inputs = resolve_command_inputs(
377
+ schema=PROMPT_SCHEMA,
378
+ provided={"prompt": prompt},
379
+ interactive=interactive,
380
+ usage="Usage: chatimg codex generate [PROMPT] [-i|-I]",
381
+ )
382
+ prompt = inputs["prompt"]
383
+
384
+ try:
385
+ generator = create_generator(
386
+ "codex",
387
+ base_url=base_url,
388
+ host_model=host_model,
389
+ image_model=image_model,
390
+ aspect_ratio=aspect_ratio,
391
+ timeout_seconds=timeout,
392
+ )
393
+ click.echo(
394
+ "Generating image with Codex "
395
+ f"(host: {generator.host_model}, image: {generator.image_model})..."
396
+ )
397
+ result = generator.generate(prompt)
398
+ output_path = resolve_generated_output_path(
399
+ output,
400
+ provider="codex",
401
+ model=generator.image_model,
402
+ prompt=prompt,
403
+ )
404
+ saved = save_binary(result, output_path)
405
+ click.echo(f"Image saved to {saved}")
406
+ except Exception as e:
407
+ raise click.ClickException(str(e)) from e
408
+
409
+
410
+ @codex.command(name="list-models")
411
+ def codex_list_models():
412
+ """List built-in Codex image model presets."""
413
+ try:
414
+ generator = create_generator("codex")
415
+ models = generator.get_models()
416
+ echo_model_list(models, "Available image models for Codex:")
417
+ except Exception as e:
418
+ raise click.ClickException(str(e)) from e
419
+
420
+
421
+ if __name__ == "__main__":
422
+ main()