comfyui-skill-cli 0.2.2__tar.gz → 0.2.3__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.
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/PKG-INFO +1 -1
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/PKG-INFO +1 -1
- comfyui_skill_cli-0.2.3/comfyui_skills_cli/__init__.py +1 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/run.py +58 -16
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/workflow.py +41 -11
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/main.py +8 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/pyproject.toml +1 -1
- comfyui_skill_cli-0.2.2/comfyui_skills_cli/commands/__init__.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/LICENSE +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/README.md +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/SOURCES.txt +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/dependency_links.txt +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/entry_points.txt +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/requires.txt +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/top_level.txt +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/__main__.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/client.py +0 -0
- {comfyui_skill_cli-0.2.2/comfyui_skills_cli → comfyui_skill_cli-0.2.3/comfyui_skills_cli/commands}/__init__.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/config.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/deps.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/history.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/server.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/skill.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/commands/upload.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/config.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/output.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skills_cli/storage.py +0 -0
- {comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.3"
|
|
@@ -4,11 +4,15 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import copy
|
|
6
6
|
import json
|
|
7
|
+
import logging
|
|
7
8
|
import os
|
|
8
9
|
import time
|
|
9
10
|
import uuid
|
|
11
|
+
from pathlib import Path
|
|
10
12
|
from typing import Any
|
|
11
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
12
16
|
import typer
|
|
13
17
|
|
|
14
18
|
from ..client import ComfyUIClient
|
|
@@ -28,7 +32,7 @@ def run_cmd(
|
|
|
28
32
|
):
|
|
29
33
|
"""Execute a skill (blocking — waits for completion)."""
|
|
30
34
|
base_dir, server_id, workflow_id = _resolve_skill(ctx, skill_id)
|
|
31
|
-
client, schema_data, workflow_data = _prepare(ctx, base_dir, server_id, workflow_id)
|
|
35
|
+
client, schema_data, workflow_data, server_config = _prepare(ctx, base_dir, server_id, workflow_id)
|
|
32
36
|
|
|
33
37
|
input_args = _parse_args(ctx, args)
|
|
34
38
|
parameters = _get_parameters(schema_data)
|
|
@@ -65,8 +69,9 @@ def run_cmd(
|
|
|
65
69
|
status_info = history.get("status", {})
|
|
66
70
|
|
|
67
71
|
if status_info.get("completed", False) or outputs:
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
collected = _collect_outputs(outputs)
|
|
73
|
+
collected = _download_outputs(client, collected, base_dir, server_config)
|
|
74
|
+
output_event(ctx, "completed", prompt_id=prompt_id, outputs=collected)
|
|
70
75
|
|
|
71
76
|
# Final result — json and stream-json both get this
|
|
72
77
|
if fmt == OutputFormat.STREAM_JSON:
|
|
@@ -74,7 +79,7 @@ def run_cmd(
|
|
|
74
79
|
output_result(ctx, {
|
|
75
80
|
"status": "success",
|
|
76
81
|
"prompt_id": prompt_id,
|
|
77
|
-
"outputs":
|
|
82
|
+
"outputs": collected,
|
|
78
83
|
})
|
|
79
84
|
return
|
|
80
85
|
|
|
@@ -118,7 +123,7 @@ def submit_cmd(
|
|
|
118
123
|
):
|
|
119
124
|
"""Submit a skill for execution (non-blocking — returns immediately)."""
|
|
120
125
|
base_dir, server_id, workflow_id = _resolve_skill(ctx, skill_id)
|
|
121
|
-
client, schema_data, workflow_data = _prepare(ctx, base_dir, server_id, workflow_id)
|
|
126
|
+
client, schema_data, workflow_data, _server_config = _prepare(ctx, base_dir, server_id, workflow_id)
|
|
122
127
|
|
|
123
128
|
input_args = _parse_args(ctx, args)
|
|
124
129
|
parameters = _get_parameters(schema_data)
|
|
@@ -157,8 +162,8 @@ def status_cmd(
|
|
|
157
162
|
status_info = history.get("status", {})
|
|
158
163
|
outputs = history.get("outputs", {})
|
|
159
164
|
if status_info.get("completed", False) or outputs:
|
|
160
|
-
|
|
161
|
-
output_result(ctx, {"status": "success", "prompt_id": prompt_id, "outputs":
|
|
165
|
+
collected = _collect_outputs(outputs)
|
|
166
|
+
output_result(ctx, {"status": "success", "prompt_id": prompt_id, "outputs": collected})
|
|
162
167
|
return
|
|
163
168
|
if status_info.get("status_str") == "error":
|
|
164
169
|
output_result(ctx, {"status": "error", "prompt_id": prompt_id, "error": _format_errors(history)})
|
|
@@ -203,7 +208,7 @@ def _prepare(ctx: typer.Context, base_dir: Any, server_id: str, workflow_id: str
|
|
|
203
208
|
output_error(ctx, "SKILL_NOT_FOUND", f'Skill "{server_id}/{workflow_id}" not found.')
|
|
204
209
|
|
|
205
210
|
client = _build_client(server_config)
|
|
206
|
-
return client, schema_data or {}, workflow_data
|
|
211
|
+
return client, schema_data or {}, workflow_data, server_config
|
|
207
212
|
|
|
208
213
|
|
|
209
214
|
def _build_client(server_config: dict[str, Any]) -> ComfyUIClient:
|
|
@@ -249,18 +254,55 @@ def _inject_params(
|
|
|
249
254
|
return workflow
|
|
250
255
|
|
|
251
256
|
|
|
257
|
+
_MEDIA_KEYS: dict[str, str] = {
|
|
258
|
+
"images": "image",
|
|
259
|
+
"audio": "audio",
|
|
260
|
+
"gifs": "image",
|
|
261
|
+
"video": "video",
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
252
265
|
def _collect_outputs(outputs: dict[str, Any]) -> list[dict[str, str]]:
|
|
253
|
-
|
|
266
|
+
collected: list[dict[str, str]] = []
|
|
254
267
|
for node_output in outputs.values():
|
|
255
268
|
if not isinstance(node_output, dict):
|
|
256
269
|
continue
|
|
257
|
-
for
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
for key, media_type in _MEDIA_KEYS.items():
|
|
271
|
+
for item in node_output.get(key, []):
|
|
272
|
+
collected.append({
|
|
273
|
+
"filename": item.get("filename", ""),
|
|
274
|
+
"subfolder": item.get("subfolder", ""),
|
|
275
|
+
"type": item.get("type", "output"),
|
|
276
|
+
"media_type": media_type,
|
|
277
|
+
})
|
|
278
|
+
return collected
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _download_outputs(
|
|
282
|
+
client: ComfyUIClient,
|
|
283
|
+
outputs: list[dict[str, str]],
|
|
284
|
+
base_dir: Path,
|
|
285
|
+
server_config: dict[str, Any],
|
|
286
|
+
) -> list[dict[str, str]]:
|
|
287
|
+
raw_dir = str(server_config.get("output_dir", "./outputs")).strip() or "./outputs"
|
|
288
|
+
output_dir = Path(raw_dir) if Path(raw_dir).is_absolute() else base_dir / raw_dir
|
|
289
|
+
for item in outputs:
|
|
290
|
+
try:
|
|
291
|
+
data = client.download_output(
|
|
292
|
+
item["filename"],
|
|
293
|
+
item.get("subfolder", ""),
|
|
294
|
+
item.get("type", "output"),
|
|
295
|
+
)
|
|
296
|
+
subfolder = item.get("subfolder", "")
|
|
297
|
+
local_dir = output_dir / subfolder if subfolder else output_dir
|
|
298
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
local_path = local_dir / item["filename"]
|
|
300
|
+
local_path.write_bytes(data)
|
|
301
|
+
item["local_path"] = str(local_path)
|
|
302
|
+
except Exception as exc:
|
|
303
|
+
logger.warning("Failed to download %s: %s", item["filename"], exc)
|
|
304
|
+
item["local_path"] = ""
|
|
305
|
+
return outputs
|
|
264
306
|
|
|
265
307
|
|
|
266
308
|
def _format_errors(history: dict[str, Any]) -> str:
|
|
@@ -57,6 +57,20 @@ _AUTO_EXPOSE_FIELDS: dict[str, dict[str, Any]] = {
|
|
|
57
57
|
"filename_prefix": {"exposed": True, "required": False, "description": "Output file prefix"},
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
_MEDIA_TYPE_FIELDS: dict[str, dict[str, dict[str, Any]]] = {
|
|
61
|
+
"audio": {
|
|
62
|
+
"tags": {"exposed": True, "required": True, "description": "Music style/genre tags"},
|
|
63
|
+
"lyrics": {"exposed": True, "required": False, "description": "Song lyrics"},
|
|
64
|
+
"bpm": {"exposed": True, "required": False, "description": "Beats per minute"},
|
|
65
|
+
"duration": {"exposed": True, "required": False, "description": "Audio duration"},
|
|
66
|
+
"seconds": {"exposed": True, "required": False, "description": "Duration in seconds"},
|
|
67
|
+
"language": {"exposed": True, "required": False, "description": "Language code"},
|
|
68
|
+
"keyscale": {"exposed": True, "required": False, "description": "Musical key and scale"},
|
|
69
|
+
"cfg_scale": {"exposed": True, "required": False, "description": "Classifier-free guidance scale"},
|
|
70
|
+
"temperature": {"exposed": True, "required": False, "description": "Sampling temperature"},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
_LOAD_IMAGE_CLASSES = {"LoadImage", "LoadImageMask"}
|
|
61
75
|
|
|
62
76
|
|
|
@@ -70,8 +84,17 @@ def _get_type_guess(value: Any) -> str:
|
|
|
70
84
|
return "string"
|
|
71
85
|
|
|
72
86
|
|
|
73
|
-
def _extract_schema(workflow_data: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
74
|
-
"""Extract parameters from API-format workflow and build schema.
|
|
87
|
+
def _extract_schema(workflow_data: dict[str, Any], media_type: str = "image") -> dict[str, dict[str, Any]]:
|
|
88
|
+
"""Extract parameters from API-format workflow and build schema.
|
|
89
|
+
|
|
90
|
+
*media_type* selects additional field-exposure rules beyond the base
|
|
91
|
+
set. ``"image"`` (default) uses only the generic rules.
|
|
92
|
+
``"audio"`` adds audio-specific fields like tags, lyrics, bpm, etc.
|
|
93
|
+
"""
|
|
94
|
+
expose_fields = dict(_AUTO_EXPOSE_FIELDS)
|
|
95
|
+
if media_type in _MEDIA_TYPE_FIELDS:
|
|
96
|
+
expose_fields.update(_MEDIA_TYPE_FIELDS[media_type])
|
|
97
|
+
|
|
75
98
|
raw_params: list[dict[str, Any]] = []
|
|
76
99
|
|
|
77
100
|
for node_id, node in workflow_data.items():
|
|
@@ -92,8 +115,8 @@ def _extract_schema(workflow_data: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
|
92
115
|
exposed, required = True, True
|
|
93
116
|
description = "Upload an image"
|
|
94
117
|
field_type = "image"
|
|
95
|
-
elif field in
|
|
96
|
-
info =
|
|
118
|
+
elif field in expose_fields:
|
|
119
|
+
info = expose_fields[field]
|
|
97
120
|
exposed = info["exposed"]
|
|
98
121
|
required = info["required"]
|
|
99
122
|
description = info["description"]
|
|
@@ -360,11 +383,18 @@ def workflow_import(
|
|
|
360
383
|
ctx: typer.Context,
|
|
361
384
|
json_path: str = typer.Argument(None, help="Path to workflow JSON file (omit when using --from-server)"),
|
|
362
385
|
name: str = typer.Option("", "--name", "-n", help="Workflow ID (default: derived from filename)"),
|
|
386
|
+
media_type: str = typer.Option("image", "--type", "-t", help="Media type preset for parameter detection: image (default), audio"),
|
|
363
387
|
from_server: bool = typer.Option(False, "--from-server", help="Import from ComfyUI server userdata"),
|
|
364
388
|
preview: bool = typer.Option(False, "--preview", help="Preview only, don't import"),
|
|
365
389
|
check_deps: bool = typer.Option(False, "--check-deps", help="Check dependencies after import"),
|
|
366
390
|
):
|
|
367
|
-
"""Import a workflow from local JSON or ComfyUI server.
|
|
391
|
+
"""Import a workflow from local JSON or ComfyUI server.
|
|
392
|
+
|
|
393
|
+
Use --type to select a parameter detection preset. The default (image)
|
|
394
|
+
detects generic fields like seed, steps, and prompt. Use --type audio
|
|
395
|
+
to also detect audio-specific fields like tags, lyrics, bpm, duration,
|
|
396
|
+
keyscale, language, cfg_scale, and temperature.
|
|
397
|
+
"""
|
|
368
398
|
base_dir = get_base_dir(ctx.obj.get("base_dir", ""))
|
|
369
399
|
config = load_config(base_dir)
|
|
370
400
|
server_id = ctx.obj.get("server") or get_default_server_id(config)
|
|
@@ -375,9 +405,9 @@ def workflow_import(
|
|
|
375
405
|
return
|
|
376
406
|
|
|
377
407
|
if from_server:
|
|
378
|
-
_import_from_server(ctx, base_dir, server_id, server_config, name, preview, check_deps)
|
|
408
|
+
_import_from_server(ctx, base_dir, server_id, server_config, name, preview, check_deps, media_type)
|
|
379
409
|
elif json_path:
|
|
380
|
-
_import_from_file(ctx, base_dir, server_id, server_config, json_path, name, preview, check_deps)
|
|
410
|
+
_import_from_file(ctx, base_dir, server_id, server_config, json_path, name, preview, check_deps, media_type)
|
|
381
411
|
else:
|
|
382
412
|
output_error(ctx, "INVALID_ARGS", "Provide a JSON file path or use --from-server.")
|
|
383
413
|
|
|
@@ -385,7 +415,7 @@ def workflow_import(
|
|
|
385
415
|
def _import_from_file(
|
|
386
416
|
ctx: typer.Context, base_dir: Path, server_id: str,
|
|
387
417
|
server_config: dict[str, Any], json_path: str, name: str,
|
|
388
|
-
preview: bool, check_deps: bool,
|
|
418
|
+
preview: bool, check_deps: bool, media_type: str = "image",
|
|
389
419
|
) -> None:
|
|
390
420
|
if not os.path.isfile(json_path):
|
|
391
421
|
output_error(ctx, "FILE_NOT_FOUND", f'File not found: "{json_path}"')
|
|
@@ -425,7 +455,7 @@ def _import_from_file(
|
|
|
425
455
|
return
|
|
426
456
|
|
|
427
457
|
# Generate schema
|
|
428
|
-
parameters = _extract_schema(api_data)
|
|
458
|
+
parameters = _extract_schema(api_data, media_type)
|
|
429
459
|
|
|
430
460
|
if preview:
|
|
431
461
|
output_result(ctx, {
|
|
@@ -496,7 +526,7 @@ def _import_from_file(
|
|
|
496
526
|
def _import_from_server(
|
|
497
527
|
ctx: typer.Context, base_dir: Path, server_id: str,
|
|
498
528
|
server_config: dict[str, Any], name_filter: str,
|
|
499
|
-
preview: bool, check_deps: bool,
|
|
529
|
+
preview: bool, check_deps: bool, media_type: str = "image",
|
|
500
530
|
) -> None:
|
|
501
531
|
client = ComfyUIClient(
|
|
502
532
|
server_url=server_config.get("url", "http://127.0.0.1:8188"),
|
|
@@ -553,7 +583,7 @@ def _import_from_server(
|
|
|
553
583
|
|
|
554
584
|
filename = os.path.basename(wf_path)
|
|
555
585
|
workflow_id = _suggest_workflow_id(api_data, filename)
|
|
556
|
-
parameters = _extract_schema(api_data)
|
|
586
|
+
parameters = _extract_schema(api_data, media_type)
|
|
557
587
|
|
|
558
588
|
workflow_dir = base_dir / "data" / server_id / workflow_id
|
|
559
589
|
workflow_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
|
+
from . import __version__
|
|
7
8
|
from .commands import config, deps, history, run, server, skill, upload, workflow
|
|
8
9
|
|
|
9
10
|
app = typer.Typer(
|
|
@@ -15,9 +16,16 @@ app = typer.Typer(
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _version_callback(value: bool) -> None:
|
|
20
|
+
if value:
|
|
21
|
+
typer.echo(f"comfyui-skill {__version__}")
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
18
25
|
@app.callback()
|
|
19
26
|
def main(
|
|
20
27
|
ctx: typer.Context,
|
|
28
|
+
version: bool = typer.Option(False, "--version", "-V", callback=_version_callback, is_eager=True, help="Show version and exit"),
|
|
21
29
|
json_output: bool = typer.Option(False, "--json", "-j", help="JSON output (shortcut for --output-format json)"),
|
|
22
30
|
output_format: str = typer.Option("", "--output-format", help="Output format: text, json, stream-json"),
|
|
23
31
|
server_id: str = typer.Option("", "--server", "-s", help="Server ID"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{comfyui_skill_cli-0.2.2 → comfyui_skill_cli-0.2.3}/comfyui_skill_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|