@mcptoolshop/style-dataset-lab 2.3.0 → 3.0.1
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 +178 -0
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +71 -0
- package/README.md +82 -9
- package/SECURITY.md +6 -5
- package/bin/sdlab.js +359 -11
- package/lib/adapters/comfyui-runner.js +170 -0
- package/lib/adapters/comfyui-workflows.js +163 -0
- package/lib/adapters/diffusers-lora.js +7 -2
- package/lib/adapters/generic-image-caption.js +6 -1
- package/lib/args.js +200 -13
- package/lib/batch-compiler.js +122 -0
- package/lib/batch-modes.js +111 -0
- package/lib/batch-runs.js +237 -0
- package/lib/batch-sheet-render.js +209 -0
- package/lib/brief-compiler.js +625 -0
- package/lib/brief-render.js +205 -0
- package/lib/card.js +11 -4
- package/lib/comfyui.js +100 -6
- package/lib/config.js +68 -3
- package/lib/critique-engine.js +413 -0
- package/lib/critique-render.js +171 -0
- package/lib/deprecation.js +1 -1
- package/lib/eval-pack.js +94 -4
- package/lib/eval-runs.js +42 -3
- package/lib/export.js +144 -34
- package/lib/generated-provenance.js +87 -0
- package/lib/implementation-packs.js +15 -1
- package/lib/log.js +41 -4
- package/lib/paths.js +72 -3
- package/lib/refine-briefs.js +336 -0
- package/lib/reingest-selected.js +184 -0
- package/lib/reingest.js +15 -1
- package/lib/run-summary.js +132 -0
- package/lib/runtime-runs.js +218 -0
- package/lib/selections.js +375 -0
- package/lib/snapshot.js +100 -7
- package/lib/split.js +175 -17
- package/lib/training-manifests.js +17 -3
- package/lib/training-packages.js +28 -14
- package/lib/workflow-profiles.js +161 -0
- package/package.json +13 -4
- package/scripts/batch-generate.js +176 -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 +53 -0
- package/scripts/canon-bind.js +7 -9
- package/scripts/card.js +22 -25
- package/scripts/compare.js +38 -33
- package/scripts/critique-show.js +50 -0
- package/scripts/critique.js +70 -0
- package/scripts/curate.js +91 -46
- package/scripts/doctor.js +172 -6
- package/scripts/eligibility.js +17 -14
- package/scripts/eval-pack.js +13 -11
- package/scripts/eval-run.js +30 -29
- package/scripts/export.js +27 -31
- package/scripts/generate-controlnet.js +31 -17
- package/scripts/generate-identity.js +37 -20
- package/scripts/generate-ipadapter.js +22 -16
- package/scripts/generate.js +68 -13
- package/scripts/implementation-pack.js +19 -14
- package/scripts/init.js +137 -17
- package/scripts/painterly.js +8 -10
- package/scripts/refine.js +119 -0
- package/scripts/reingest-selected.js +78 -0
- package/scripts/reingest.js +37 -18
- 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 +119 -0
- package/scripts/selection-show.js +88 -0
- package/scripts/snapshot.js +22 -24
- package/scripts/split.js +26 -31
- package/scripts/training-manifest.js +29 -24
- package/scripts/training-package.js +24 -20
- package/scripts/training-profile.js +13 -8
- package/scripts/workflow-list.js +61 -0
- package/scripts/workflow-show.js +97 -0
- package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/architecture/terminology.json +10 -8
- 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/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/character-design/project.json +4 -2
- 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/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/creature-design/project.json +4 -2
- package/templates/domains/creature-design/terminology.json +1 -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/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/game-art/project.json +4 -2
- 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/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/vehicle-mech/project.json +4 -2
- 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/scripts/bulk-curate-wave2-5.js +0 -138
- package/scripts/bulk-curate-waves11-18.js +0 -413
- package/scripts/curate-wave25.js +0 -209
- package/scripts/migrate-records.js +0 -141
- package/templates/inputs/prompts/example-wave.json +0 -27
package/bin/sdlab.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { enableDebug, handleCliError, inputError } from '../lib/errors.js';
|
|
11
11
|
import { setLogLevel } from '../lib/log.js';
|
|
12
|
+
import { findClosest } from '../lib/args.js';
|
|
12
13
|
|
|
13
14
|
const COMMANDS = {
|
|
14
15
|
'init': '../scripts/init.js',
|
|
@@ -43,6 +44,240 @@ const PROJECT_COMMANDS = {
|
|
|
43
44
|
'migrate': '../scripts/migrate-records.js',
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
// Two-word commands under "workflow" namespace
|
|
48
|
+
const WORKFLOW_COMMANDS = {
|
|
49
|
+
'list': '../scripts/workflow-list.js',
|
|
50
|
+
'show': '../scripts/workflow-show.js',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Two-word commands under "brief" namespace
|
|
54
|
+
const BRIEF_COMMANDS = {
|
|
55
|
+
'compile': '../scripts/brief-compile.js',
|
|
56
|
+
'show': '../scripts/brief-show.js',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Two-word commands under "run" namespace
|
|
60
|
+
const RUN_COMMANDS = {
|
|
61
|
+
'generate': '../scripts/run-generate.js',
|
|
62
|
+
'show': '../scripts/run-show.js',
|
|
63
|
+
'list': '../scripts/run-list.js',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Two-word commands under "critique" namespace
|
|
67
|
+
const CRITIQUE_COMMANDS = {
|
|
68
|
+
'show': '../scripts/critique-show.js',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Two-word commands under "batch" namespace
|
|
72
|
+
const BATCH_COMMANDS = {
|
|
73
|
+
'generate': '../scripts/batch-generate.js',
|
|
74
|
+
'show': '../scripts/batch-show.js',
|
|
75
|
+
'sheet': '../scripts/batch-sheet.js',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Two-word commands under "selection" namespace
|
|
79
|
+
const SELECTION_COMMANDS = {
|
|
80
|
+
'show': '../scripts/selection-show.js',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ─── Per-command help ─────────────────────────────────────────────────
|
|
84
|
+
//
|
|
85
|
+
// Keyed by the user-facing command (including two-word forms joined by
|
|
86
|
+
// a space). Help text is plain — no color codes — to stay readable in
|
|
87
|
+
// dumb terminals and when piped to files.
|
|
88
|
+
|
|
89
|
+
const HELP_TEXT = {
|
|
90
|
+
'init': `sdlab init <project-name> [--domain <domain>]
|
|
91
|
+
|
|
92
|
+
Scaffold a new project from a domain template.
|
|
93
|
+
|
|
94
|
+
Positional:
|
|
95
|
+
project-name Lowercase, hyphen-separated (e.g. "my-project")
|
|
96
|
+
|
|
97
|
+
Flags:
|
|
98
|
+
--domain <id> Domain template (default: generic)
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
sdlab init my-project
|
|
102
|
+
sdlab init heist-crew --domain character-design
|
|
103
|
+
|
|
104
|
+
Run "sdlab init" with no arguments to see the available domains.`,
|
|
105
|
+
|
|
106
|
+
'project doctor': `sdlab project doctor --project <name>
|
|
107
|
+
|
|
108
|
+
Validate project config and directory structure. Reports missing
|
|
109
|
+
required files, unmigrated record schemas, and orphaned outputs.
|
|
110
|
+
|
|
111
|
+
Flags:
|
|
112
|
+
--project <name> Project to audit (required in practice)
|
|
113
|
+
--verbose Show per-check detail
|
|
114
|
+
--quiet Only print PASS / FAIL summary`,
|
|
115
|
+
|
|
116
|
+
'generate': `sdlab generate [<pack-path>] --project <name> [--dry-run]
|
|
117
|
+
|
|
118
|
+
Generate candidates from a prompt pack (JSON file under
|
|
119
|
+
projects/<name>/inputs/prompts/).
|
|
120
|
+
|
|
121
|
+
Positional:
|
|
122
|
+
pack-path Path to the prompt pack (default: inputs/prompts/rpg-icons-lane1.json)
|
|
123
|
+
|
|
124
|
+
Flags:
|
|
125
|
+
--project <name> Project to operate on
|
|
126
|
+
--dry-run Preview work without contacting ComfyUI
|
|
127
|
+
|
|
128
|
+
Env:
|
|
129
|
+
COMFY_URL ComfyUI HTTP endpoint (default: http://127.0.0.1:8188)
|
|
130
|
+
|
|
131
|
+
Precedence: CLI flag > env COMFY_URL > project defaults > built-in default.`,
|
|
132
|
+
|
|
133
|
+
'batch generate': `sdlab batch generate --mode <id> [--subject <id>] [--theme <label>] --project <name>
|
|
134
|
+
|
|
135
|
+
Execute a batch production mode (expression sheet, silhouette pack,
|
|
136
|
+
environment board, etc).
|
|
137
|
+
|
|
138
|
+
Flags:
|
|
139
|
+
--mode <id> Batch mode id (required)
|
|
140
|
+
--subject <id> Subject for subject-driven modes
|
|
141
|
+
--theme <label> Theme label for environment modes
|
|
142
|
+
--asset <id> Training asset reference
|
|
143
|
+
--project <name> Project to operate on
|
|
144
|
+
--dry-run Prepare batch without submitting to ComfyUI
|
|
145
|
+
--json Output manifest as JSON`,
|
|
146
|
+
|
|
147
|
+
'brief compile': `sdlab brief compile --profile <id> --project <name>
|
|
148
|
+
|
|
149
|
+
Compile a generation brief from project canon (constitution, lanes,
|
|
150
|
+
workflow profile).
|
|
151
|
+
|
|
152
|
+
Flags:
|
|
153
|
+
--profile <id> Workflow profile id to compile against
|
|
154
|
+
--project <name> Project to operate on
|
|
155
|
+
--out <path> Optional explicit output path`,
|
|
156
|
+
|
|
157
|
+
'run generate': `sdlab run generate --brief <id> --project <name> [--dry-run]
|
|
158
|
+
|
|
159
|
+
Execute a compiled brief through ComfyUI.
|
|
160
|
+
|
|
161
|
+
Flags:
|
|
162
|
+
--brief <id> Brief id to execute (required)
|
|
163
|
+
--project <name> Project to operate on
|
|
164
|
+
--dry-run Plan without submitting to ComfyUI`,
|
|
165
|
+
|
|
166
|
+
'critique': `sdlab critique --run <id> --project <name>
|
|
167
|
+
|
|
168
|
+
Critique a completed run. Writes a critique record alongside the run.
|
|
169
|
+
|
|
170
|
+
Flags:
|
|
171
|
+
--run <id> Run id to critique (required)
|
|
172
|
+
--project <name> Project to operate on
|
|
173
|
+
--json Emit critique as JSON on stdout`,
|
|
174
|
+
|
|
175
|
+
'refine': `sdlab refine --run <id> --pick <file> --project <name>
|
|
176
|
+
|
|
177
|
+
Generate a refined next-pass brief based on a specific pick from a
|
|
178
|
+
prior run.
|
|
179
|
+
|
|
180
|
+
Flags:
|
|
181
|
+
--run <id> Source run id (required)
|
|
182
|
+
--pick <file> Picked output filename inside the run (required)
|
|
183
|
+
--project <name> Project to operate on`,
|
|
184
|
+
|
|
185
|
+
'batch sheet': `sdlab batch sheet <batch-id> --project <name>
|
|
186
|
+
|
|
187
|
+
Re-render a batch's contact sheet from its manifest.
|
|
188
|
+
|
|
189
|
+
Positional:
|
|
190
|
+
batch-id Batch id to re-render
|
|
191
|
+
|
|
192
|
+
Flags:
|
|
193
|
+
--project <name> Project to operate on`,
|
|
194
|
+
|
|
195
|
+
'select': `sdlab select [--run <id>|--batch <id>] --approve <files> --project <name>
|
|
196
|
+
|
|
197
|
+
Select approved outputs from a run or batch.
|
|
198
|
+
|
|
199
|
+
Flags:
|
|
200
|
+
--run <id> Source run id
|
|
201
|
+
--batch <id> Source batch id
|
|
202
|
+
--approve <files> Comma-separated output filenames to approve
|
|
203
|
+
--project <name> Project to operate on`,
|
|
204
|
+
|
|
205
|
+
'curate': `sdlab curate <asset_id> <approved|rejected|borderline> <explanation> [--scores k:v,...] [--failures f1,f2] --project <name>
|
|
206
|
+
|
|
207
|
+
Record a judgment on a candidate and move it to the matching outputs
|
|
208
|
+
subdirectory.
|
|
209
|
+
|
|
210
|
+
Positional:
|
|
211
|
+
asset_id Candidate asset id
|
|
212
|
+
status approved | rejected | borderline
|
|
213
|
+
explanation Short reason (quoted)
|
|
214
|
+
|
|
215
|
+
Flags:
|
|
216
|
+
--scores k:v,... Per-dimension scores (e.g. silhouette:0.9,palette:0.8)
|
|
217
|
+
--failures f1,f2 Named failure tags (comma-separated)
|
|
218
|
+
--notes "…" Optional free-form note
|
|
219
|
+
--list Print uncurated candidates and exit
|
|
220
|
+
--dry-run Preview without moving files
|
|
221
|
+
--project <name> Project to operate on`,
|
|
222
|
+
|
|
223
|
+
'canon-bind': `sdlab canon-bind --project <name> [--lane <id>]
|
|
224
|
+
|
|
225
|
+
Bind approved records to constitution rules.
|
|
226
|
+
|
|
227
|
+
Flags:
|
|
228
|
+
--project <name> Project to operate on
|
|
229
|
+
--lane <id> Restrict binding to a single lane`,
|
|
230
|
+
|
|
231
|
+
'snapshot': `sdlab snapshot <create|list|show|diff> [args] --project <name>
|
|
232
|
+
|
|
233
|
+
Manage frozen dataset snapshots.
|
|
234
|
+
|
|
235
|
+
Subcommands:
|
|
236
|
+
create Create a new snapshot from approved records
|
|
237
|
+
list List all snapshots (default subcommand)
|
|
238
|
+
show <id> Show snapshot details
|
|
239
|
+
diff <a> <b> Compare two snapshots
|
|
240
|
+
|
|
241
|
+
Flags:
|
|
242
|
+
--profile <name> Selection profile (create only)
|
|
243
|
+
--project <name> Project to operate on
|
|
244
|
+
--dry-run Preview snapshot contents without writing`,
|
|
245
|
+
|
|
246
|
+
'split': `sdlab split <build|list|show|audit> [args] --project <name>
|
|
247
|
+
|
|
248
|
+
Manage train/val/test splits from a snapshot.
|
|
249
|
+
|
|
250
|
+
Subcommands:
|
|
251
|
+
build Build a split from a snapshot
|
|
252
|
+
list List all splits
|
|
253
|
+
show <id> Show split details
|
|
254
|
+
audit <id> Audit a split for leakage and balance
|
|
255
|
+
|
|
256
|
+
Flags:
|
|
257
|
+
--project <name> Project to operate on`,
|
|
258
|
+
|
|
259
|
+
'export': `sdlab export <build|list> [args] --project <name>
|
|
260
|
+
|
|
261
|
+
Build and list self-contained export packages.
|
|
262
|
+
|
|
263
|
+
Subcommands:
|
|
264
|
+
build Build a new export package
|
|
265
|
+
list List all export packages
|
|
266
|
+
|
|
267
|
+
Flags:
|
|
268
|
+
--project <name> Project to operate on`,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// "bind" is a short alias for "canon-bind" — share help.
|
|
272
|
+
HELP_TEXT['bind'] = HELP_TEXT['canon-bind'];
|
|
273
|
+
|
|
274
|
+
function printCommandHelp(key) {
|
|
275
|
+
const text = HELP_TEXT[key];
|
|
276
|
+
if (!text) return false;
|
|
277
|
+
console.log(text);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
46
281
|
function printHelp() {
|
|
47
282
|
console.log(`\x1b[1msdlab\x1b[0m — Style Dataset Lab CLI\n`);
|
|
48
283
|
console.log('Usage: sdlab <command> [options]\n');
|
|
@@ -58,9 +293,10 @@ function printHelp() {
|
|
|
58
293
|
console.log(' generate:ipadapter IP-Adapter reference-guided generation');
|
|
59
294
|
console.log(' curate Move candidate to approved/rejected/borderline');
|
|
60
295
|
console.log(' compare Record pairwise A-vs-B comparison');
|
|
61
|
-
console.log(' bind
|
|
296
|
+
console.log(' canon-bind Bind approved records to constitution rules');
|
|
297
|
+
console.log(' ("bind" is a short alias for canon-bind)');
|
|
62
298
|
console.log(' painterly Post-processing painterly style pass');
|
|
63
|
-
console.log(' painterly:test
|
|
299
|
+
console.log(' painterly:test Calibrate painterly denoise level on sample images');
|
|
64
300
|
console.log('');
|
|
65
301
|
console.log('Dataset:');
|
|
66
302
|
console.log(' snapshot create Create a frozen dataset snapshot');
|
|
@@ -96,26 +332,83 @@ function printHelp() {
|
|
|
96
332
|
console.log(' implementation-pack build Build implementation example pack');
|
|
97
333
|
console.log(' implementation-pack show <id> Show pack details');
|
|
98
334
|
console.log(' implementation-pack list List all packs');
|
|
99
|
-
console.log(' reingest generated --source <dir> Re-ingest generated outputs');
|
|
335
|
+
console.log(' reingest generated --source <dir> --manifest <id> Re-ingest generated outputs');
|
|
336
|
+
console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
|
|
100
337
|
console.log(' reingest audit Audit re-ingested record status');
|
|
101
338
|
console.log('');
|
|
339
|
+
console.log('Production:');
|
|
340
|
+
console.log(' workflow list List workflow profiles');
|
|
341
|
+
console.log(' workflow show <id> Show workflow profile details');
|
|
342
|
+
console.log(' brief compile Compile generation brief from project truth');
|
|
343
|
+
console.log(' brief show <id> Show compiled brief');
|
|
344
|
+
console.log(' run generate --brief <id> Execute brief through ComfyUI');
|
|
345
|
+
console.log(' run show <id> Show run details');
|
|
346
|
+
console.log(' run list List all runs');
|
|
347
|
+
console.log(' critique --run <id> Critique a completed run');
|
|
348
|
+
console.log(' critique show --run <id> Show saved critique');
|
|
349
|
+
console.log(' refine --run <id> --pick <file> Generate refined next-pass brief');
|
|
350
|
+
console.log(' batch generate --mode <id> Execute a batch production mode');
|
|
351
|
+
console.log(' batch show [batch-id] List batches or show details');
|
|
352
|
+
console.log(' batch sheet <batch-id> Re-render batch sheet from manifest');
|
|
353
|
+
console.log(' select --run <id> --approve <files> Select approved outputs from a run');
|
|
354
|
+
console.log(' select --batch <id> --approve ... Select approved outputs from a batch');
|
|
355
|
+
console.log(' selection show [selection-id] List selections or show details');
|
|
356
|
+
console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
|
|
357
|
+
console.log('');
|
|
102
358
|
console.log('Options:');
|
|
103
|
-
console.log(' --project <name> Project to operate on (default
|
|
359
|
+
console.log(' --project <name> Project to operate on (no default — fallback to "star-freight" with warning)');
|
|
104
360
|
console.log(' --game <name> Deprecated alias for --project');
|
|
105
361
|
console.log(' --debug Show stack traces on error');
|
|
106
362
|
console.log(' --verbose Verbose output');
|
|
107
363
|
console.log(' --quiet Suppress non-essential output');
|
|
108
364
|
console.log(' --dry-run Preview changes without writing (where supported)');
|
|
109
|
-
console.log(' --help Show this help');
|
|
365
|
+
console.log(' --help Show this help (or per-command help with "sdlab <cmd> --help")');
|
|
110
366
|
console.log('');
|
|
111
367
|
console.log('Examples:');
|
|
112
368
|
console.log(' sdlab init my-project --domain character-design');
|
|
369
|
+
console.log(' sdlab generate --help');
|
|
113
370
|
console.log(' sdlab snapshot create --project star-freight');
|
|
114
371
|
console.log(' sdlab split build --dry-run --project star-freight');
|
|
115
372
|
console.log(' sdlab training-package build --debug --project star-freight');
|
|
116
373
|
}
|
|
117
374
|
|
|
375
|
+
// All top-level command tokens users can type (including two-word heads).
|
|
376
|
+
function allKnownCommands() {
|
|
377
|
+
const oneWord = Object.keys(COMMANDS);
|
|
378
|
+
const heads = ['project', 'workflow', 'brief', 'run', 'critique', 'refine', 'batch', 'select', 'selection'];
|
|
379
|
+
return [...new Set([...oneWord, ...heads])];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function argvHasHelp(argv) {
|
|
383
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── Signal + fatal-error handlers ────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
function installProcessHandlers() {
|
|
389
|
+
const onSignal = (sig) => {
|
|
390
|
+
const code = sig === 'SIGINT' ? 130 : 143;
|
|
391
|
+
process.stderr.write(`\n\x1b[31m✗ Interrupted by ${sig}. Partial output may exist.\x1b[0m\n`);
|
|
392
|
+
process.exit(code);
|
|
393
|
+
};
|
|
394
|
+
process.on('SIGINT', () => onSignal('SIGINT'));
|
|
395
|
+
process.on('SIGTERM', () => onSignal('SIGTERM'));
|
|
396
|
+
|
|
397
|
+
process.on('uncaughtException', (err) => {
|
|
398
|
+
handleCliError(err);
|
|
399
|
+
// handleCliError exits, but guard anyway.
|
|
400
|
+
process.exit(2);
|
|
401
|
+
});
|
|
402
|
+
process.on('unhandledRejection', (reason) => {
|
|
403
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
404
|
+
handleCliError(err);
|
|
405
|
+
process.exit(2);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
118
409
|
async function main() {
|
|
410
|
+
installProcessHandlers();
|
|
411
|
+
|
|
119
412
|
const args = process.argv.slice(2);
|
|
120
413
|
|
|
121
414
|
// Global flags
|
|
@@ -131,20 +424,75 @@ async function main() {
|
|
|
131
424
|
let command = args[0];
|
|
132
425
|
let commandArgs = args.slice(1);
|
|
133
426
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
427
|
+
// ── Two-word dispatch tables and --help interception ──
|
|
428
|
+
// For every two-word namespace, we check "cmd sub --help" first,
|
|
429
|
+
// then the single-word "cmd --help", then fall through to dispatch.
|
|
430
|
+
|
|
431
|
+
const twoWord = (head, table) => ({ head, table });
|
|
432
|
+
const namespaces = [
|
|
433
|
+
twoWord('project', PROJECT_COMMANDS),
|
|
434
|
+
twoWord('workflow', WORKFLOW_COMMANDS),
|
|
435
|
+
twoWord('brief', BRIEF_COMMANDS),
|
|
436
|
+
twoWord('run', RUN_COMMANDS),
|
|
437
|
+
twoWord('batch', BATCH_COMMANDS),
|
|
438
|
+
twoWord('selection', SELECTION_COMMANDS),
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
// "project", "workflow", "brief", "run", "batch", "selection" two-word forms
|
|
442
|
+
for (const { head, table } of namespaces) {
|
|
443
|
+
if (command === head && args[1] && table[args[1]]) {
|
|
444
|
+
commandArgs = args.slice(2);
|
|
445
|
+
const twoWordKey = `${head} ${args[1]}`;
|
|
446
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(twoWordKey)) return;
|
|
447
|
+
const mod = await import(table[args[1]]);
|
|
448
|
+
await mod.run(commandArgs);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// "critique" — bare or "critique show"
|
|
454
|
+
if (command === 'critique') {
|
|
455
|
+
if (args[1] && CRITIQUE_COMMANDS[args[1]]) {
|
|
456
|
+
commandArgs = args.slice(2);
|
|
457
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(`critique ${args[1]}`)) return;
|
|
458
|
+
const mod = await import(CRITIQUE_COMMANDS[args[1]]);
|
|
459
|
+
await mod.run(commandArgs);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('critique')) return;
|
|
463
|
+
const mod = await import('../scripts/critique.js');
|
|
464
|
+
await mod.run(commandArgs);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// "refine" — single command
|
|
469
|
+
if (command === 'refine') {
|
|
470
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('refine')) return;
|
|
471
|
+
const mod = await import('../scripts/refine.js');
|
|
472
|
+
await mod.run(commandArgs);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// "select" — single command
|
|
477
|
+
if (command === 'select') {
|
|
478
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('select')) return;
|
|
479
|
+
const mod = await import('../scripts/select.js');
|
|
139
480
|
await mod.run(commandArgs);
|
|
140
481
|
return;
|
|
141
482
|
}
|
|
142
483
|
|
|
143
484
|
const modulePath = COMMANDS[command];
|
|
144
485
|
if (!modulePath) {
|
|
145
|
-
|
|
486
|
+
const suggestion = findClosest(command, allKnownCommands());
|
|
487
|
+
const hint = suggestion
|
|
488
|
+
? `Did you mean "${suggestion}"? (Run "sdlab --help" for all commands.)`
|
|
489
|
+
: 'Run "sdlab --help" for available commands.';
|
|
490
|
+
throw inputError('INPUT_UNKNOWN_COMMAND', `Unknown command: ${command}`, hint);
|
|
146
491
|
}
|
|
147
492
|
|
|
493
|
+
// Per-command --help for one-word commands
|
|
494
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(command)) return;
|
|
495
|
+
|
|
148
496
|
const mod = await import(modulePath);
|
|
149
497
|
await mod.run(commandArgs);
|
|
150
498
|
}
|
|
@@ -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
|
+
}
|