@mcptoolshop/style-dataset-lab 2.3.0 → 3.0.0
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.
- package/CHANGELOG.md +125 -0
- package/README.md +36 -8
- package/bin/sdlab.js +129 -0
- package/lib/adapters/comfyui-runner.js +170 -0
- package/lib/adapters/comfyui-workflows.js +163 -0
- package/lib/batch-compiler.js +118 -0
- package/lib/batch-modes.js +111 -0
- package/lib/batch-runs.js +168 -0
- package/lib/batch-sheet-render.js +206 -0
- package/lib/brief-compiler.js +598 -0
- package/lib/brief-render.js +205 -0
- package/lib/critique-engine.js +413 -0
- package/lib/critique-render.js +171 -0
- package/lib/generated-provenance.js +87 -0
- package/lib/paths.js +31 -0
- package/lib/refine-briefs.js +329 -0
- package/lib/reingest-selected.js +173 -0
- package/lib/run-summary.js +131 -0
- package/lib/runtime-runs.js +168 -0
- package/lib/selections.js +323 -0
- package/lib/workflow-profiles.js +161 -0
- package/package.json +6 -3
- package/scripts/batch-generate.js +167 -0
- package/scripts/batch-sheet.js +46 -0
- package/scripts/batch-show.js +86 -0
- package/scripts/brief-compile.js +134 -0
- package/scripts/brief-show.js +52 -0
- package/scripts/critique-show.js +50 -0
- package/scripts/critique.js +70 -0
- package/scripts/doctor.js +169 -1
- package/scripts/init.js +22 -0
- package/scripts/refine.js +119 -0
- package/scripts/reingest-selected.js +78 -0
- package/scripts/reingest.js +7 -1
- package/scripts/run-generate.js +76 -0
- package/scripts/run-list.js +58 -0
- package/scripts/run-show.js +63 -0
- package/scripts/select.js +118 -0
- package/scripts/selection-show.js +88 -0
- package/scripts/workflow-list.js +61 -0
- package/scripts/workflow-show.js +96 -0
- package/templates/domains/architecture/workflows/profiles/environment-moodboard.json +62 -0
- package/templates/domains/architecture/workflows/profiles/interior-variant-pack.json +60 -0
- package/templates/domains/character-design/workflows/profiles/character-portrait-set.json +61 -0
- package/templates/domains/character-design/workflows/profiles/expression-sheet.json +59 -0
- package/templates/domains/creature-design/workflows/profiles/creature-portrait-set.json +61 -0
- package/templates/domains/creature-design/workflows/profiles/silhouette-exploration.json +58 -0
- package/templates/domains/game-art/workflows/profiles/character-portrait-set.json +62 -0
- package/templates/domains/game-art/workflows/profiles/environment-moodboard.json +61 -0
- package/templates/domains/vehicle-mech/workflows/profiles/variant-pack.json +60 -0
- package/templates/domains/vehicle-mech/workflows/profiles/vehicle-silhouette-pack.json +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,131 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.6.0] - 2026-07-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Batch production modes (Phase 4D)** — coordinated multi-slot generation with reviewable sheet output
|
|
10
|
+
- `sdlab batch generate --mode <id>` — execute a batch production mode (expression sheets, environment boards, etc.)
|
|
11
|
+
- `sdlab batch show [batch-id]` — list all batches or show batch details
|
|
12
|
+
- `sdlab batch sheet <batch-id>` — re-render batch sheet from saved manifest
|
|
13
|
+
- `--dry-run` flag prepares batch directory and briefs without submitting to ComfyUI
|
|
14
|
+
- **Batch modes** (`lib/batch-modes.js`) — load, validate, and list batch mode definitions from `workflows/batch-modes/`
|
|
15
|
+
- **Batch compiler** (`lib/batch-compiler.js`) — expand one batch mode into multiple slot briefs with controlled per-slot deltas
|
|
16
|
+
- **Batch runs** (`lib/batch-runs.js`) — batch directory creation (`batch_YYYY-MM-DD_NNN`), coordinated slot execution, manifest save/load
|
|
17
|
+
- **Batch sheet renderer** (`lib/batch-sheet-render.js`) — HTML review surfaces with CSS grid layout, dark theme, zero dependencies
|
|
18
|
+
- **4 starter batch modes** for Star Freight: expression-sheet, environment-board, silhouette-pack, continuity-variants
|
|
19
|
+
- **JSON schemas** for batch mode definitions and batch manifests (`schemas/`)
|
|
20
|
+
- Doctor validates batch mode definitions (batch_type, subject_mode, slot uniqueness, layout, workflow references)
|
|
21
|
+
- Init scaffolds `workflows/batch-modes/` and `batches/` directories
|
|
22
|
+
## [3.0.0] - 2026-04-16
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Selection + Reintegration (Phase 4E)** — close the production loop from generation back to corpus
|
|
27
|
+
- `sdlab select --run <id> --approve <files>` — select approved outputs from a run
|
|
28
|
+
- `sdlab select --batch <id> --approve <slot:file,...>` — select approved outputs from a batch
|
|
29
|
+
- `sdlab selection show [selection-id]` — list selections or show details
|
|
30
|
+
- `sdlab reingest selected --selection <id>` — re-ingest selected outputs as candidate records
|
|
31
|
+
- `--reason` and `--tags` flags for selection context
|
|
32
|
+
- `--dry-run` and `--json` output support
|
|
33
|
+
- **Selection engine** (`lib/selections.js`) — create selection artifacts from runs or batches
|
|
34
|
+
- Selection ID generation (`selection_YYYY-MM-DD_NNN`)
|
|
35
|
+
- Run and batch selection with output validation
|
|
36
|
+
- Chosen files copied into `selections/<id>/chosen/`
|
|
37
|
+
- Manifest, summary.json, and summary.md written per selection
|
|
38
|
+
- **Generated provenance** (`lib/generated-provenance.js`) — build provenance blocks for re-ingested records
|
|
39
|
+
- Traces back through run → brief → workflow → seed → config fingerprint
|
|
40
|
+
- Normalizes run and batch source context into a single shape
|
|
41
|
+
- **Re-ingest from selections** (`lib/reingest-selected.js`) — create candidate records from selections
|
|
42
|
+
- Records carry `source: generated` provenance with full generation provenance block
|
|
43
|
+
- Images copied to `inbox/generated/`
|
|
44
|
+
- Records tagged: `generated`, `selected`, workflow ID, subject ID
|
|
45
|
+
- No auto-bypass around review — records are staged for normal curation
|
|
46
|
+
- Provenance log written as `provenance.jsonl`
|
|
47
|
+
- **JSON schemas** for selections and generated records (`schemas/`)
|
|
48
|
+
- Doctor validates new project directories (`selections/`, `inbox/generated/`)
|
|
49
|
+
- Init scaffolds `selections/` and `inbox/generated/` directories
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **README** rewritten to describe the full production loop: canon → dataset → training → brief → run → critique → batch → select → re-ingest
|
|
54
|
+
- **Landing page** updated: new headline, description, 4-tab preview (Start, Dataset, Produce, Close the loop), 7 feature cards including production workflow and selection
|
|
55
|
+
- **Handbook** updated: index shows production loop diagram, new [Production Loop](handbook/production-loop/) page with complete end-to-end walkthrough
|
|
56
|
+
- **Package metadata** updated: description, keywords (`generation-workflow`, `batch-production`, `reintegration`), version badge
|
|
57
|
+
- Version bumped to **v3.0.0** — the repo is now a full production workbench, not just a dataset preparation system
|
|
58
|
+
## [2.5.0] - 2026-04-16
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- **Critique + Refine loop (Phase 4C)** — inspect run outputs and generate focused next-pass briefs
|
|
63
|
+
- `sdlab critique --run <id>` — generate structured critique of a completed run
|
|
64
|
+
- `sdlab critique show --run <id>` — display saved critique (text, JSON, markdown)
|
|
65
|
+
- `sdlab refine --run <id> --pick <file>` — generate refined next-pass brief from critique
|
|
66
|
+
- CLI override flags: `--preserve`, `--push`, `--suppress` for manual delta control
|
|
67
|
+
- **Critique engine** (`lib/critique-engine.js`) — brief-driven candidate analysis
|
|
68
|
+
- 10 critique dimensions activated by workflow canon focus and drift guards
|
|
69
|
+
- Per-candidate structured notes: strengths, drift issues, preserve/correct next pass
|
|
70
|
+
- Run-level recommendation: accept_one, refine_from_one, rerun_broader, discard_run
|
|
71
|
+
- Workflow-specific emphasis by output mode (portrait, expression, moodboard, etc.)
|
|
72
|
+
- **Refine briefs** (`lib/refine-briefs.js`) — delta-layered brief generation
|
|
73
|
+
- Preserve/push/suppress instructions derived from critique + CLI overrides
|
|
74
|
+
- Prompt refinement: original prompt preserved, delta instructions appended
|
|
75
|
+
- Negative prompt extension: suppress items added without replacing existing negatives
|
|
76
|
+
- Refined briefs point back to parent brief + source run + picked candidate
|
|
77
|
+
- **JSON schemas** for critique reports and refined briefs (`schemas/`)
|
|
78
|
+
- Critique and refine artifacts stored in run directory: `runs/<id>/critique.json`, `runs/<id>/refine/`
|
|
79
|
+
|
|
80
|
+
## [2.4.0] - 2026-04-16
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- **ComfyUI runtime adapter (Phase 4B)** — execute compiled briefs through ComfyUI and capture run artifacts
|
|
85
|
+
- `sdlab run generate --brief <id>` — execute a brief through ComfyUI, capture outputs to runs/
|
|
86
|
+
- `sdlab run show <id>` — show run manifest and results
|
|
87
|
+
- `sdlab run list` — list all runs for a project
|
|
88
|
+
- `--dry-run` flag prepares run directory without submitting to ComfyUI
|
|
89
|
+
- `--seed <n>` override for reproducible generation
|
|
90
|
+
- **Run management** (`lib/runtime-runs.js`) — run ID generation (`run_YYYY-MM-DD_NNN`), seed plans (fixed/increment/random), run directory preparation, manifest save/load
|
|
91
|
+
- **ComfyUI workflow adapter** (`lib/adapters/comfyui-workflows.js`) — builds ComfyUI prompt graphs from compiled briefs, template resolution by output mode
|
|
92
|
+
- **ComfyUI runner** (`lib/adapters/comfyui-runner.js`) — orchestrates seed plan → graph build → submit → poll → download → manifest for each image
|
|
93
|
+
- **Run summaries** (`lib/run-summary.js`) — generates summary.json and summary.md in each run directory
|
|
94
|
+
- **Runtime templates** (`runtime/comfyui/txt2img-standard.json`) — declarative workflow template metadata
|
|
95
|
+
- Doctor validates runtime templates (Section 10)
|
|
96
|
+
- `runs/` added to project scaffold
|
|
97
|
+
|
|
98
|
+
## [2.3.0] - 2026-04-15
|
|
99
|
+
|
|
100
|
+
### Added
|
|
101
|
+
|
|
102
|
+
- **Production workbench spine (Phase 4A)** — workflow profiles and brief compiler
|
|
103
|
+
- `sdlab workflow list` — list workflow profiles for a project
|
|
104
|
+
- `sdlab workflow show <id>` — show workflow profile details
|
|
105
|
+
- `sdlab brief compile` — compile a deterministic generation brief from project truth
|
|
106
|
+
- `sdlab brief show <id>` — show compiled brief (text, JSON, or markdown)
|
|
107
|
+
- **Workflow profiles** — production recipes defining output shape, canon constraints, drift guards, and runtime defaults
|
|
108
|
+
- 6 output modes: portrait_set, expression_sheet, variant_pack, moodboard, silhouette_sheet, turnaround
|
|
109
|
+
- 12 canon focus categories: silhouette, material_language, palette, anatomy, costume_logic, era_logic, scale_logic, lighting, surface_wear, gesture, composition, faction_read
|
|
110
|
+
- Subject mode enforcement: required, optional, forbidden
|
|
111
|
+
- **Brief compiler** — merges 6 layers of project truth into a frozen generation contract:
|
|
112
|
+
- Project canon → lane config → subject identity → training asset hints → workflow profile → CLI overrides
|
|
113
|
+
- Structured prompt assembly (no freeform prompt soup)
|
|
114
|
+
- Deterministic output: same inputs produce identical briefs
|
|
115
|
+
- Config fingerprint (SHA-256) for change detection
|
|
116
|
+
- Outputs JSON + human-readable Markdown
|
|
117
|
+
- **3 starter workflows for Star Freight**: character-portrait-set, expression-sheet, environment-moodboard
|
|
118
|
+
- **10 domain template workflows** across 5 domains (game-art, character-design, creature-design, architecture, vehicle-mech)
|
|
119
|
+
- **New library modules:** `lib/workflow-profiles.js`, `lib/brief-compiler.js`, `lib/brief-render.js`
|
|
120
|
+
- **Doctor validates workflow profiles** — checks schema, lane references, subject mode, runtime defaults
|
|
121
|
+
- **Init scaffolds `workflows/profiles/` and `briefs/`** directories and copies domain workflow templates
|
|
122
|
+
|
|
123
|
+
### Key properties
|
|
124
|
+
|
|
125
|
+
- Workflow profiles are NOT training profiles — they drive generation, not training
|
|
126
|
+
- Briefs are frozen contracts — the creative runtime boundary between planning and execution
|
|
127
|
+
- Subject constraints are hard when subject_mode = required (fail loudly if missing)
|
|
128
|
+
- Drift guards appear as explicit warnings, never hidden in metadata
|
|
129
|
+
|
|
5
130
|
## [2.2.0] - 2026-04-16
|
|
6
131
|
|
|
7
132
|
### Added
|
package/README.md
CHANGED
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
Write your visual rules. Generate art. Judge every image against those rules. Ship the results as versioned, auditable training data.
|
|
14
|
+
Write your visual rules. Generate art. Judge every image against those rules. Ship the results as versioned, auditable training data — then put trained models to work in real production workflows and feed the best outputs back into your corpus.
|
|
15
15
|
|
|
16
|
-
Style Dataset Lab connects the thing you wrote down about your art style to the dataset you actually train from. You define a constitution — silhouette rules, palette constraints, material language, whatever matters for your project. The pipeline generates candidates, scores them against those rules, and packages the approved work into reproducible datasets where every record explains why it was included.
|
|
16
|
+
Style Dataset Lab connects the thing you wrote down about your art style to the dataset you actually train from, and then closes the loop all the way through production. You define a constitution — silhouette rules, palette constraints, material language, whatever matters for your project. The pipeline generates candidates, scores them against those rules, and packages the approved work into reproducible datasets where every record explains why it was included.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Then the production workbench takes over: compile generation briefs from project truth, run them through ComfyUI, critique the outputs, batch-produce expression sheets and environment boards, select the best results, and re-ingest them as new candidates. The loop closes: produce, select, review, strengthen.
|
|
19
19
|
|
|
20
20
|
## The pipeline
|
|
21
21
|
|
|
@@ -35,18 +35,29 @@ sdlab snapshot create --project my-project
|
|
|
35
35
|
sdlab split build
|
|
36
36
|
sdlab export build
|
|
37
37
|
|
|
38
|
-
# Build a training package
|
|
38
|
+
# Build a training package
|
|
39
39
|
sdlab training-manifest create --profile character-style-lora
|
|
40
40
|
sdlab training-package build
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
|
|
42
|
+
# Compile a production brief and run it
|
|
43
|
+
sdlab brief compile --workflow character-portrait-set --subject kael_maren
|
|
44
|
+
sdlab run generate --brief brief_2026-04-16_001
|
|
45
|
+
|
|
46
|
+
# Critique, refine, batch-produce
|
|
47
|
+
sdlab critique --run run_2026-04-16_001
|
|
48
|
+
sdlab refine --run run_2026-04-16_001 --pick 001.png
|
|
49
|
+
sdlab batch generate --mode expression-sheet --subject kael_maren
|
|
50
|
+
|
|
51
|
+
# Select the best outputs and bring them back
|
|
52
|
+
sdlab select --run run_2026-04-16_001 --approve 001.png,003.png
|
|
53
|
+
sdlab reingest selected --selection selection_2026-04-16_001
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
That last command is the point.
|
|
56
|
+
That last command is the point. Selected outputs come back through the same review process as everything else. The corpus grows and the rules hold.
|
|
46
57
|
|
|
47
58
|
## What it produces
|
|
48
59
|
|
|
49
|
-
Seven
|
|
60
|
+
Seven dataset artifacts and a full production workbench. Each artifact links to its predecessors so you can trace any training record back to the rule that approved it.
|
|
50
61
|
|
|
51
62
|
| Artifact | What it is |
|
|
52
63
|
|----------|-----------|
|
|
@@ -58,6 +69,17 @@ Seven versioned, checksummed artifacts. Each links to its predecessors so you ca
|
|
|
58
69
|
| **Eval scorecard** | Per-task pass/fail from scoring generated outputs against eval packs. |
|
|
59
70
|
| **Implementation pack** | Prompt examples, known failures, continuity tests, and re-ingest guidance. |
|
|
60
71
|
|
|
72
|
+
The production workbench adds:
|
|
73
|
+
|
|
74
|
+
| Surface | What it does |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| **Compiled brief** | Deterministic generation instruction from workflow profile + project truth. |
|
|
77
|
+
| **Run** | Frozen execution artifact: brief + seeds + ComfyUI outputs + manifest. |
|
|
78
|
+
| **Critique** | Structured multi-dimension evaluation of run outputs against canon. |
|
|
79
|
+
| **Batch** | Coordinated multi-slot production (expression sheets, environment boards, silhouette packs). |
|
|
80
|
+
| **Selection** | Creative decision artifact: which outputs were chosen, why, and where they came from. |
|
|
81
|
+
| **Re-ingest** | Selected outputs return as candidate records with full generation provenance. |
|
|
82
|
+
|
|
61
83
|
## Why this exists
|
|
62
84
|
|
|
63
85
|
Training data is the highest-leverage artifact in any visual AI pipeline. But most training data is a folder of images with no history, no judgment trail, and no connection to the style rules it was supposed to follow.
|
|
@@ -94,6 +116,12 @@ projects/my-project/
|
|
|
94
116
|
splits/ Train/val/test partitions
|
|
95
117
|
exports/ Versioned export packages
|
|
96
118
|
training/ Profiles, manifests, packages, eval runs, implementations
|
|
119
|
+
workflows/ Workflow profiles + batch mode definitions
|
|
120
|
+
briefs/ Compiled generation briefs
|
|
121
|
+
runs/ Execution artifacts (brief + outputs + manifest)
|
|
122
|
+
batches/ Coordinated multi-slot productions
|
|
123
|
+
selections/ Chosen outputs with reasons and provenance
|
|
124
|
+
inbox/generated/ Re-ingested images awaiting review
|
|
97
125
|
```
|
|
98
126
|
|
|
99
127
|
## Trust properties
|
package/bin/sdlab.js
CHANGED
|
@@ -43,6 +43,42 @@ const PROJECT_COMMANDS = {
|
|
|
43
43
|
'migrate': '../scripts/migrate-records.js',
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
// Two-word commands under "workflow" namespace
|
|
47
|
+
const WORKFLOW_COMMANDS = {
|
|
48
|
+
'list': '../scripts/workflow-list.js',
|
|
49
|
+
'show': '../scripts/workflow-show.js',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Two-word commands under "brief" namespace
|
|
53
|
+
const BRIEF_COMMANDS = {
|
|
54
|
+
'compile': '../scripts/brief-compile.js',
|
|
55
|
+
'show': '../scripts/brief-show.js',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Two-word commands under "run" namespace
|
|
59
|
+
const RUN_COMMANDS = {
|
|
60
|
+
'generate': '../scripts/run-generate.js',
|
|
61
|
+
'show': '../scripts/run-show.js',
|
|
62
|
+
'list': '../scripts/run-list.js',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Two-word commands under "critique" namespace
|
|
66
|
+
const CRITIQUE_COMMANDS = {
|
|
67
|
+
'show': '../scripts/critique-show.js',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Two-word commands under "batch" namespace
|
|
71
|
+
const BATCH_COMMANDS = {
|
|
72
|
+
'generate': '../scripts/batch-generate.js',
|
|
73
|
+
'show': '../scripts/batch-show.js',
|
|
74
|
+
'sheet': '../scripts/batch-sheet.js',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Two-word commands under "selection" namespace
|
|
78
|
+
const SELECTION_COMMANDS = {
|
|
79
|
+
'show': '../scripts/selection-show.js',
|
|
80
|
+
};
|
|
81
|
+
|
|
46
82
|
function printHelp() {
|
|
47
83
|
console.log(`\x1b[1msdlab\x1b[0m — Style Dataset Lab CLI\n`);
|
|
48
84
|
console.log('Usage: sdlab <command> [options]\n');
|
|
@@ -99,6 +135,25 @@ function printHelp() {
|
|
|
99
135
|
console.log(' reingest generated --source <dir> Re-ingest generated outputs');
|
|
100
136
|
console.log(' reingest audit Audit re-ingested record status');
|
|
101
137
|
console.log('');
|
|
138
|
+
console.log('Production:');
|
|
139
|
+
console.log(' workflow list List workflow profiles');
|
|
140
|
+
console.log(' workflow show <id> Show workflow profile details');
|
|
141
|
+
console.log(' brief compile Compile generation brief from project truth');
|
|
142
|
+
console.log(' brief show <id> Show compiled brief');
|
|
143
|
+
console.log(' run generate --brief <id> Execute brief through ComfyUI');
|
|
144
|
+
console.log(' run show <id> Show run details');
|
|
145
|
+
console.log(' run list List all runs');
|
|
146
|
+
console.log(' critique --run <id> Critique a completed run');
|
|
147
|
+
console.log(' critique show --run <id> Show saved critique');
|
|
148
|
+
console.log(' refine --run <id> --pick <file> Generate refined next-pass brief');
|
|
149
|
+
console.log(' batch generate --mode <id> Execute a batch production mode');
|
|
150
|
+
console.log(' batch show [batch-id] List batches or show details');
|
|
151
|
+
console.log(' batch sheet <batch-id> Re-render batch sheet from manifest');
|
|
152
|
+
console.log(' select --run <id> --approve <files> Select approved outputs from a run');
|
|
153
|
+
console.log(' select --batch <id> --approve ... Select approved outputs from a batch');
|
|
154
|
+
console.log(' selection show [selection-id] List selections or show details');
|
|
155
|
+
console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
|
|
156
|
+
console.log('');
|
|
102
157
|
console.log('Options:');
|
|
103
158
|
console.log(' --project <name> Project to operate on (default: star-freight)');
|
|
104
159
|
console.log(' --game <name> Deprecated alias for --project');
|
|
@@ -140,6 +195,80 @@ async function main() {
|
|
|
140
195
|
return;
|
|
141
196
|
}
|
|
142
197
|
|
|
198
|
+
// Handle "workflow <subcommand>" two-word form
|
|
199
|
+
if (command === 'workflow' && args[1] && WORKFLOW_COMMANDS[args[1]]) {
|
|
200
|
+
commandArgs = args.slice(2);
|
|
201
|
+
const modulePath = WORKFLOW_COMMANDS[args[1]];
|
|
202
|
+
const mod = await import(modulePath);
|
|
203
|
+
await mod.run(commandArgs);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle "brief <subcommand>" two-word form
|
|
208
|
+
if (command === 'brief' && args[1] && BRIEF_COMMANDS[args[1]]) {
|
|
209
|
+
commandArgs = args.slice(2);
|
|
210
|
+
const modulePath = BRIEF_COMMANDS[args[1]];
|
|
211
|
+
const mod = await import(modulePath);
|
|
212
|
+
await mod.run(commandArgs);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle "run <subcommand>" two-word form
|
|
217
|
+
if (command === 'run' && args[1] && RUN_COMMANDS[args[1]]) {
|
|
218
|
+
commandArgs = args.slice(2);
|
|
219
|
+
const modulePath = RUN_COMMANDS[args[1]];
|
|
220
|
+
const mod = await import(modulePath);
|
|
221
|
+
await mod.run(commandArgs);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Handle "critique" — bare or "critique show" two-word form
|
|
226
|
+
if (command === 'critique') {
|
|
227
|
+
if (args[1] && CRITIQUE_COMMANDS[args[1]]) {
|
|
228
|
+
commandArgs = args.slice(2);
|
|
229
|
+
const modulePath = CRITIQUE_COMMANDS[args[1]];
|
|
230
|
+
const mod = await import(modulePath);
|
|
231
|
+
await mod.run(commandArgs);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Bare "critique" → generate critique
|
|
235
|
+
const mod = await import('../scripts/critique.js');
|
|
236
|
+
await mod.run(commandArgs);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle "refine" — single command
|
|
241
|
+
if (command === 'refine') {
|
|
242
|
+
const mod = await import('../scripts/refine.js');
|
|
243
|
+
await mod.run(commandArgs);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Handle "batch <subcommand>" two-word form
|
|
248
|
+
if (command === 'batch' && args[1] && BATCH_COMMANDS[args[1]]) {
|
|
249
|
+
commandArgs = args.slice(2);
|
|
250
|
+
const modulePath = BATCH_COMMANDS[args[1]];
|
|
251
|
+
const mod = await import(modulePath);
|
|
252
|
+
await mod.run(commandArgs);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Handle "select" — single command
|
|
257
|
+
if (command === 'select') {
|
|
258
|
+
const mod = await import('../scripts/select.js');
|
|
259
|
+
await mod.run(commandArgs);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Handle "selection <subcommand>" two-word form
|
|
264
|
+
if (command === 'selection' && args[1] && SELECTION_COMMANDS[args[1]]) {
|
|
265
|
+
commandArgs = args.slice(2);
|
|
266
|
+
const modulePath = SELECTION_COMMANDS[args[1]];
|
|
267
|
+
const mod = await import(modulePath);
|
|
268
|
+
await mod.run(commandArgs);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
143
272
|
const modulePath = COMMANDS[command];
|
|
144
273
|
if (!modulePath) {
|
|
145
274
|
throw inputError('INPUT_UNKNOWN_COMMAND', `Unknown command: ${command}`, 'Run "sdlab --help" for available commands.');
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComfyUI runner — executes a brief through ComfyUI and captures outputs.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates: seed plan → graph build → submit → poll → download → manifest.
|
|
5
|
+
* Each image in the seed plan becomes one submission to ComfyUI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { comfyHealth, submitAndWait, downloadImage } from '../comfyui.js';
|
|
11
|
+
import { buildWorkflowGraph, resolveTemplateId } from './comfyui-workflows.js';
|
|
12
|
+
import { buildSeedPlan, prepareRunDir, saveRunManifest } from '../runtime-runs.js';
|
|
13
|
+
import { loadProjectConfig } from '../config.js';
|
|
14
|
+
import { log, info, warn } from '../log.js';
|
|
15
|
+
import { runtimeError } from '../errors.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Execute a brief: build graphs, submit to ComfyUI, capture outputs.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} opts
|
|
21
|
+
* @param {string} opts.projectRoot — absolute project path
|
|
22
|
+
* @param {string} opts.projectName — project name
|
|
23
|
+
* @param {Object} opts.brief — compiled brief object
|
|
24
|
+
* @param {number} [opts.baseSeed] — override base seed
|
|
25
|
+
* @param {boolean} [opts.dryRun] — if true, prepare run dir but do not submit
|
|
26
|
+
* @returns {Promise<Object>} — manifest
|
|
27
|
+
*/
|
|
28
|
+
export async function executeRun({ projectRoot, projectName, brief, baseSeed, dryRun = false }) {
|
|
29
|
+
const config = loadProjectConfig(projectRoot);
|
|
30
|
+
const defaults = config.meta?.defaults ?? {};
|
|
31
|
+
const comfyUrl = defaults.comfy_url || process.env.COMFY_URL || 'http://127.0.0.1:8188';
|
|
32
|
+
|
|
33
|
+
// Resolve template
|
|
34
|
+
const outputMode = brief.expected_outputs?.output_mode || 'portrait_set';
|
|
35
|
+
const templateId = resolveTemplateId(outputMode);
|
|
36
|
+
const outputCount = brief.expected_outputs?.output_count || brief.runtime_plan?.output_count || 4;
|
|
37
|
+
|
|
38
|
+
// Build seed plan
|
|
39
|
+
const seedMode = brief.runtime_plan?.seed_mode || 'increment';
|
|
40
|
+
const seedPlan = buildSeedPlan({ seedMode, outputCount, baseSeed });
|
|
41
|
+
|
|
42
|
+
// Prepare run directory
|
|
43
|
+
const { runId, runDir } = await prepareRunDir(projectRoot, brief);
|
|
44
|
+
|
|
45
|
+
info(`Run ${runId} — ${outputCount} images, seed mode: ${seedMode}, base: ${seedPlan.base_seed}`);
|
|
46
|
+
info(`Template: ${templateId}, ComfyUI: ${comfyUrl}`);
|
|
47
|
+
|
|
48
|
+
// Health check
|
|
49
|
+
if (!dryRun) {
|
|
50
|
+
const healthy = await comfyHealth(comfyUrl);
|
|
51
|
+
if (!healthy) {
|
|
52
|
+
throw runtimeError(
|
|
53
|
+
'COMFYUI_OFFLINE',
|
|
54
|
+
`ComfyUI not reachable at ${comfyUrl}`,
|
|
55
|
+
'Start ComfyUI or set COMFY_URL / project defaults.comfy_url'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
info('ComfyUI online');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const outputs = [];
|
|
62
|
+
const errors = [];
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < outputCount; i++) {
|
|
65
|
+
const seed = seedPlan.seeds[i];
|
|
66
|
+
const imageIndex = String(i + 1).padStart(3, '0');
|
|
67
|
+
|
|
68
|
+
log(` [${i + 1}/${outputCount}] seed: ${seed}`);
|
|
69
|
+
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
outputs.push({
|
|
72
|
+
index: i,
|
|
73
|
+
seed,
|
|
74
|
+
status: 'dry_run',
|
|
75
|
+
filename: `${imageIndex}.png`,
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const startMs = Date.now();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Build graph
|
|
84
|
+
const { nodes } = buildWorkflowGraph({
|
|
85
|
+
brief,
|
|
86
|
+
projectDefaults: defaults,
|
|
87
|
+
seed,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Submit and wait
|
|
91
|
+
const result = await submitAndWait(nodes, comfyUrl, {
|
|
92
|
+
clientPrefix: `sdl-run-${runId}`,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const elapsed = Date.now() - startMs;
|
|
96
|
+
|
|
97
|
+
// Extract output image
|
|
98
|
+
const nodeOutputs = result.outputs || {};
|
|
99
|
+
let imageFile = null;
|
|
100
|
+
let imageSubfolder = '';
|
|
101
|
+
for (const nodeOut of Object.values(nodeOutputs)) {
|
|
102
|
+
if (nodeOut.images && nodeOut.images.length > 0) {
|
|
103
|
+
imageFile = nodeOut.images[0].filename;
|
|
104
|
+
imageSubfolder = nodeOut.images[0].subfolder || '';
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!imageFile) {
|
|
110
|
+
warn(` No output image for seed ${seed}`);
|
|
111
|
+
outputs.push({ index: i, seed, status: 'no_output', elapsed_ms: elapsed });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Download and save
|
|
116
|
+
const imgData = await downloadImage(imageFile, imageSubfolder, comfyUrl);
|
|
117
|
+
const destFilename = `${imageIndex}.png`;
|
|
118
|
+
await writeFile(join(runDir, 'outputs', destFilename), imgData);
|
|
119
|
+
|
|
120
|
+
outputs.push({
|
|
121
|
+
index: i,
|
|
122
|
+
seed,
|
|
123
|
+
status: 'ok',
|
|
124
|
+
filename: destFilename,
|
|
125
|
+
bytes: imgData.length,
|
|
126
|
+
elapsed_ms: elapsed,
|
|
127
|
+
comfy_filename: imageFile,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
log(` ✓ ${destFilename} (${imgData.length} bytes, ${(elapsed / 1000).toFixed(1)}s)`);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const elapsed = Date.now() - startMs;
|
|
133
|
+
warn(` ✗ seed ${seed}: ${err.message}`);
|
|
134
|
+
errors.push({ index: i, seed, error: err.message });
|
|
135
|
+
outputs.push({
|
|
136
|
+
index: i,
|
|
137
|
+
seed,
|
|
138
|
+
status: 'error',
|
|
139
|
+
error: err.message,
|
|
140
|
+
elapsed_ms: elapsed,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Build manifest
|
|
146
|
+
const manifest = {
|
|
147
|
+
run_id: runId,
|
|
148
|
+
brief_id: brief.brief_id,
|
|
149
|
+
project_id: brief.project_id,
|
|
150
|
+
workflow_template_id: templateId,
|
|
151
|
+
adapter_target: 'comfyui',
|
|
152
|
+
output_mode: outputMode,
|
|
153
|
+
output_count: outputCount,
|
|
154
|
+
created_at: new Date().toISOString(),
|
|
155
|
+
seed_plan: seedPlan,
|
|
156
|
+
comfy_url: comfyUrl,
|
|
157
|
+
checkpoint: defaults.checkpoint,
|
|
158
|
+
loras: defaults.loras || [],
|
|
159
|
+
runtime_plan: brief.runtime_plan,
|
|
160
|
+
outputs,
|
|
161
|
+
errors,
|
|
162
|
+
dry_run: dryRun,
|
|
163
|
+
success_count: outputs.filter(o => o.status === 'ok' || o.status === 'dry_run').length,
|
|
164
|
+
error_count: errors.length,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await saveRunManifest(runDir, manifest);
|
|
168
|
+
|
|
169
|
+
return manifest;
|
|
170
|
+
}
|