From 700e47e2cc1ab60eb8235360c8427270f0c1a0ad Mon Sep 17 00:00:00 2001 From: bymyself Date: Fri, 11 Sep 2026 00:47:46 -0700 Subject: [PATCH 01/13] feat(workflow): add insert-workflow command emitting stamped insert_workflow op --- comfy_cli/command/workflow.py | 3 + comfy_cli/command/workflow_edit.py | 24 +++++ comfy_cli/error_codes.py | 6 ++ comfy_cli/workflow_ops.py | 115 ++++++++++++++++++++- docs/op-vocabulary-v1.md | 29 +++++- tests/comfy_cli/test_insert_workflow_op.py | 57 ++++++++++ 6 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 tests/comfy_cli/test_insert_workflow_op.py diff --git a/comfy_cli/command/workflow.py b/comfy_cli/command/workflow.py index 850bd918b..ad5cf94b4 100644 --- a/comfy_cli/command/workflow.py +++ b/comfy_cli/command/workflow.py @@ -1934,6 +1934,9 @@ def validate_cmd( from comfy_cli.command import workflow_edit as _wedit # noqa: E402 +app.command("insert-workflow", help="Insert a complete workflow; emits one insert_workflow op.")( + _wedit.insert_workflow_cmd +) app.command("add-node", help="Add a node to the graph; emits an add_node op.")(_wedit.add_node_cmd) app.command("connect", help="Wire an output slot to an input slot; emits a connect op.")(_wedit.connect_cmd) app.command("set-widget", help="Set a widget by name (`.`); emits a set_widget op.")(_wedit.set_widget_cmd) diff --git a/comfy_cli/command/workflow_edit.py b/comfy_cli/command/workflow_edit.py index 39e039740..2f80c40bd 100644 --- a/comfy_cli/command/workflow_edit.py +++ b/comfy_cli/command/workflow_edit.py @@ -137,6 +137,30 @@ def _graph_or_exit(input_path, host, port, renderer, where=None): # --------------------------------------------------------------------------- +@tracking.track_command("workflow") +def insert_workflow_cmd( + file: Annotated[str, typer.Argument(help="Frontend-format workflow JSON to update.")], + template: Annotated[str, typer.Argument(help="Frontend-format workflow JSON to insert.")], + actor: ActorOpt = "cli", + base_version: BaseVersionOpt = 0, + stdout: StdoutOpt = False, +): + """Insert a workflow template and emit one atomic ``insert_workflow`` op.""" + renderer = get_renderer() + renderer.command = "workflow insert-workflow" + p, workflow = _load_workflow_or_fail(renderer, file) + try: + template_path = Path(template).expanduser() + inserted = json.loads(template_path.read_text(encoding="utf-8")) + if not isinstance(inserted, dict): + raise ValueError("template must be a JSON object") + workflow, op = workflow_ops.insert_workflow(workflow, inserted, actor=actor, base_version=base_version) + except (OSError, json.JSONDecodeError, UnicodeDecodeError, ValueError) as e: + _emit_edit_error(renderer, e, hint="provide a frontend-format workflow template JSON file") + raise typer.Exit(code=1) from e + _finish(renderer, p, workflow, op, base_version, stdout, "workflow insert-workflow") + + @tracking.track_command("workflow") def add_node_cmd( file: Annotated[str, typer.Argument(help="Frontend-format workflow JSON.")], diff --git a/comfy_cli/error_codes.py b/comfy_cli/error_codes.py index d5de7ac85..c7b81db3c 100644 --- a/comfy_cli/error_codes.py +++ b/comfy_cli/error_codes.py @@ -600,6 +600,12 @@ class ErrorCode: "(docs/op-vocabulary-v1.md: batchable = no) and the batch was rejected atomically — nothing was applied.", "run the standalone `comfy workflow reset-doc --confirm` first, then apply the remaining ops as a batch", ), + ErrorCode( + "workflow_insert_workflow_not_batchable", + "A batch contained an `insert_workflow` op. A complete workflow insertion is one standalone atomic op, " + "so nesting it in the spec batch protocol is rejected and nothing is applied.", + "run `comfy workflow insert-workflow