@mcptoolshop/style-dataset-lab 1.2.1 → 2.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 +45 -0
- package/README.md +95 -116
- package/bin/sdlab.js +103 -0
- package/lib/args.js +106 -0
- package/lib/comfyui.js +109 -0
- package/lib/config.js +104 -0
- package/lib/deprecation.js +14 -0
- package/lib/paths.js +38 -0
- package/lib/records.js +69 -0
- package/package.json +13 -4
- package/scripts/bulk-curate-wave2-5.js +1 -1
- package/scripts/bulk-curate-waves11-18.js +1 -1
- package/scripts/canon-bind.js +268 -332
- package/scripts/compare.js +40 -30
- package/scripts/curate-wave25.js +1 -1
- package/scripts/curate.js +34 -28
- package/scripts/doctor.js +321 -0
- package/scripts/generate-controlnet.js +36 -121
- package/scripts/generate-identity.js +38 -106
- package/scripts/generate-ipadapter.js +42 -119
- package/scripts/generate.js +22 -81
- package/scripts/init.js +226 -0
- package/scripts/migrate-records.js +15 -21
- package/scripts/painterly-test.js +20 -47
- package/scripts/painterly.js +27 -106
- package/templates/domains/architecture/constitution.json +97 -0
- package/templates/domains/architecture/lanes.json +41 -0
- package/templates/domains/architecture/project.json +20 -0
- package/templates/domains/architecture/rubric.json +40 -0
- package/templates/domains/architecture/terminology.json +19 -0
- package/templates/domains/character-design/constitution.json +88 -0
- package/templates/domains/character-design/lanes.json +35 -0
- package/templates/domains/character-design/project.json +17 -0
- package/templates/domains/character-design/rubric.json +38 -0
- package/templates/domains/character-design/terminology.json +21 -0
- package/templates/domains/creature-design/constitution.json +87 -0
- package/templates/domains/creature-design/lanes.json +41 -0
- package/templates/domains/creature-design/project.json +18 -0
- package/templates/domains/creature-design/rubric.json +39 -0
- package/templates/domains/creature-design/terminology.json +20 -0
- package/templates/domains/game-art/constitution.json +113 -0
- package/templates/domains/game-art/lanes.json +47 -0
- package/templates/domains/game-art/project.json +17 -0
- package/templates/domains/game-art/rubric.json +36 -0
- package/templates/domains/game-art/terminology.json +30 -0
- package/templates/domains/vehicle-mech/constitution.json +87 -0
- package/templates/domains/vehicle-mech/lanes.json +41 -0
- package/templates/domains/vehicle-mech/project.json +17 -0
- package/templates/domains/vehicle-mech/rubric.json +40 -0
- package/templates/domains/vehicle-mech/terminology.json +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.0.0] - 2026-04-15
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- **`games/` renamed to `projects/`** -- all project data now lives under `projects/<name>/`
|
|
10
|
+
- **`--game` renamed to `--project`** -- `--game` still works with a deprecation warning (will be removed in v3.0)
|
|
11
|
+
- **Canon-bind loads from config files** -- rules, lanes, rubric, and terminology are now per-project JSON, not hardcoded
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`sdlab` CLI** -- unified command-line interface (`bin/sdlab.js`)
|
|
16
|
+
- `sdlab init <name> --domain <domain>` -- scaffold projects from domain templates
|
|
17
|
+
- `sdlab project doctor` -- validate project config and structure
|
|
18
|
+
- `sdlab bind`, `sdlab curate`, `sdlab generate`, etc. -- all pipeline commands
|
|
19
|
+
- **Shared library** (`lib/`) -- 6 modules replacing duplicated code across 13 scripts
|
|
20
|
+
- `lib/args.js` -- unified argument parser with `--project` and deprecated `--game` support
|
|
21
|
+
- `lib/paths.js` -- centralized path resolution
|
|
22
|
+
- `lib/comfyui.js` -- ComfyUI HTTP client (extracted from 5 scripts)
|
|
23
|
+
- `lib/config.js` -- per-project JSON config loader
|
|
24
|
+
- `lib/records.js` -- record I/O utilities
|
|
25
|
+
- `lib/deprecation.js` -- one-shot deprecation warnings
|
|
26
|
+
- **Per-project config files** -- 5 JSON files per project:
|
|
27
|
+
- `project.json` -- identity, domain, generation defaults
|
|
28
|
+
- `constitution.json` -- rules with rationale templates
|
|
29
|
+
- `lanes.json` -- subject lanes with regex detection patterns
|
|
30
|
+
- `rubric.json` -- scoring dimensions, thresholds, failure-to-rule mappings
|
|
31
|
+
- `terminology.json` -- group vocabulary with separate `id_detection_order` and `prompt_detection_order`
|
|
32
|
+
- **5 domain starter templates** -- game-art, character-design, creature-design, architecture, vehicle-mech
|
|
33
|
+
- **Training profile placeholders** in domain templates (eligible lanes, thresholds, export format)
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Canon-bind is now fully config-driven -- loads rules, lanes, dimensions, thresholds, group vocabulary, and rationale templates from per-project JSON files
|
|
38
|
+
- Missing config files cause hard failure with actionable error messages (no silent fallback)
|
|
39
|
+
- All 10 CLI-exposed scripts refactored to export `run(argv)` for CLI dispatch
|
|
40
|
+
- Net code reduction: ~305 lines removed from deduplication
|
|
41
|
+
|
|
42
|
+
### Migration
|
|
43
|
+
|
|
44
|
+
1. Rename `games/` to `projects/`
|
|
45
|
+
2. Use `--project` instead of `--game` (or keep `--game` with deprecation warning)
|
|
46
|
+
3. For canon-bind: add 5 JSON config files to your project (use `sdlab init` as reference)
|
|
47
|
+
|
|
48
|
+
Star Freight config files are included as the canonical example.
|
|
49
|
+
|
|
5
50
|
## [1.2.0] - 2026-04-14
|
|
6
51
|
|
|
7
52
|
- Added `templates/` with blank constitution, review rubric, and example prompt pack
|
package/README.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
<p align="center">
|
|
6
2
|
<img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/style-dataset-lab/readme.png" width="400">
|
|
7
3
|
</p>
|
|
@@ -13,163 +9,146 @@
|
|
|
13
9
|
|
|
14
10
|
# style-dataset-lab
|
|
15
11
|
|
|
16
|
-
A
|
|
12
|
+
A canon-bound visual dataset pipeline for directed image production.
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
Define what your project looks like. Generate candidates. Curate against your canon. Bind approved work to constitution rules. Export trustworthy datasets for training, evaluation, and production reuse.
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
## What this is
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
A **visual canon + dataset pipeline** for teams that care about style consistency, provenance, and reviewable judgment. Every asset carries three things:
|
|
23
19
|
|
|
24
|
-
1. **
|
|
25
|
-
2. **Canon
|
|
26
|
-
3. **Quality judgment** -- approved/rejected with per-dimension scores
|
|
20
|
+
1. **Provenance** -- full generation history (checkpoint, LoRA, seed, sampler, cfg, timing)
|
|
21
|
+
2. **Canon binding** -- which constitution rules this asset passes, fails, or partially meets
|
|
22
|
+
3. **Quality judgment** -- approved/rejected/borderline with per-dimension scores
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
The pipeline works for game art, character design, creature design, architecture, vehicle/mech concepts, and any domain where visual production needs to stay on-model.
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
## Quick start
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
```bash
|
|
29
|
+
# Install the CLI + pipeline
|
|
30
|
+
npm install -g @mcptoolshop/style-dataset-lab
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
# Scaffold a new project
|
|
33
|
+
sdlab init my-project --domain character-design
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
# Validate the project structure
|
|
36
|
+
sdlab project doctor --project my-project
|
|
37
|
+
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Available domains: `game-art`, `character-design`, `creature-design`, `architecture`, `vehicle-mech`, or `generic`.
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## CLI
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
sdlab init <name> [--domain <domain>] # Scaffold a new project
|
|
45
|
+
sdlab project doctor [--project <name>] # Validate project config
|
|
46
|
+
|
|
47
|
+
sdlab generate <pack> [--project <name>] # Generate candidates via ComfyUI
|
|
48
|
+
sdlab generate:identity <packet> # Named-subject identity images
|
|
49
|
+
sdlab generate:controlnet # ControlNet-guided generation
|
|
50
|
+
sdlab generate:ipadapter # IP-Adapter reference-guided
|
|
51
|
+
|
|
52
|
+
sdlab curate <id> <status> <explanation> # Record review judgment
|
|
53
|
+
sdlab compare <a> <b> <winner> <reason> # Pairwise A-vs-B comparison
|
|
54
|
+
sdlab bind [--project <name>] # Bind records to constitution rules
|
|
55
|
+
sdlab painterly [--project <name>] # Post-processing style pass
|
|
51
56
|
```
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# Copy templates into your game directory
|
|
57
|
-
mkdir -p games/my-game/{records,comparisons,inputs/prompts,outputs/{candidates,approved,rejected,borderline,painterly},exports}
|
|
58
|
-
cp -r templates/canon games/my-game/canon
|
|
59
|
-
cp templates/inputs/prompts/example-wave.json games/my-game/inputs/prompts/wave1.json
|
|
60
|
-
# Edit the canon and prompts, then generate
|
|
61
|
-
```
|
|
58
|
+
All commands accept `--project <name>` (default: `star-freight`).
|
|
62
59
|
|
|
63
|
-
##
|
|
60
|
+
## Project model
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
Each project is a self-contained directory under `projects/` with its own canon, config, and data:
|
|
66
63
|
|
|
67
64
|
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<your-game>/ Add more games with the same structure
|
|
65
|
+
projects/
|
|
66
|
+
my-project/
|
|
67
|
+
project.json Project identity + generation defaults
|
|
68
|
+
constitution.json Rules array with rationale templates
|
|
69
|
+
lanes.json Subject lanes with detection patterns
|
|
70
|
+
rubric.json Scoring dimensions + thresholds
|
|
71
|
+
terminology.json Group vocabulary + detection order
|
|
72
|
+
canon/ Style constitution (markdown)
|
|
73
|
+
records/ Per-asset JSON (provenance + judgment + canon)
|
|
74
|
+
inputs/prompts/ Prompt packs (JSON)
|
|
75
|
+
outputs/ Generated images (gitignored)
|
|
76
|
+
comparisons/ A-vs-B preference judgments
|
|
77
|
+
exports/ Training data output (gitignored)
|
|
82
78
|
```
|
|
83
79
|
|
|
84
|
-
## Pipeline
|
|
80
|
+
## Pipeline
|
|
85
81
|
|
|
86
|
-
|
|
82
|
+
```
|
|
83
|
+
canon --> prompts --> generate --> curate --> bind --> compare --> export
|
|
84
|
+
| | | | | | |
|
|
85
|
+
rules packs ComfyUI judgment assertions preferences dataset
|
|
86
|
+
```
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
1. **Define canon** -- write your style constitution and review rubric
|
|
89
|
+
2. **Create prompt packs** -- structured subjects with style prefix and variations
|
|
90
|
+
3. **Generate** -- ComfyUI produces candidates with full provenance
|
|
91
|
+
4. **Curate** -- approve/reject with per-dimension scores and failure modes
|
|
92
|
+
5. **Bind** -- link each asset to constitution rules with pass/fail/partial verdicts
|
|
93
|
+
6. **Compare** -- record pairwise preferences for DPO/ranking datasets
|
|
94
|
+
7. **Export** -- produce training data via [`repo-dataset`](https://github.com/mcp-tool-shop-org/repo-dataset) in 10+ formats
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
# (see templates/inputs/prompts/example-wave.json)
|
|
96
|
+
## Domain templates
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
npm run generate -- --game star-freight inputs/prompts/wave1.json
|
|
97
|
-
npm run generate -- --game star-freight inputs/prompts/wave1.json --dry-run
|
|
98
|
+
Each domain template ships with lane definitions, constitution rules, scoring rubrics, and terminology structures designed for that production context:
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
| Domain | Lanes | Key concerns |
|
|
101
|
+
|--------|-------|-------------|
|
|
102
|
+
| **game-art** | character, environment, prop, UI, ship, interior, equipment | Silhouette at gameplay scale, faction differentiation, wear/aging |
|
|
103
|
+
| **character-design** | portrait, full_body, turnaround, expression_sheet, action_pose | Proportion accuracy, costume logic, personality read, gesture clarity |
|
|
104
|
+
| **creature-design** | concept, orthographic, detail_study, action, scale_reference, habitat | Anatomical plausibility, evolutionary logic, silhouette distinction |
|
|
105
|
+
| **architecture** | exterior, interior, streetscape, structural_detail, ruin, landscape | Structural plausibility, material consistency, perspective, era coherence |
|
|
106
|
+
| **vehicle-mech** | exterior, cockpit, component, schematic, silhouette_sheet, damage_variant | Mechanical logic, functional design language, access points, damage narrative |
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
npm run curate -- --game star-freight <asset_id> approved "explanation"
|
|
104
|
-
npm run curate -- --game star-freight <asset_id> rejected "explanation" --failures "too_clean"
|
|
108
|
+
## The dataset angle
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
npm run painterly -- --game star-freight
|
|
110
|
+
The pipeline produces more than organized images. It produces **trustworthy, structured datasets** with:
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
- **Full provenance** on every asset (reproducible generations)
|
|
113
|
+
- **Canon-bound judgments** (not just labels -- reasoned verdicts against declared rules)
|
|
114
|
+
- **Pairwise preferences** (for DPO and ranking-based training)
|
|
115
|
+
- **Per-dimension scoring** (fine-grained quality signals, not just pass/fail)
|
|
116
|
+
- **Failure mode tagging** (what specifically went wrong and which rules were violated)
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
npm run compare -- --game star-freight <asset_a> <asset_b> a "A has better faction read"
|
|
118
|
+
Export via `repo-dataset` to TRL, LLaVA, Qwen2-VL, JSONL, Parquet, and more.
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
repo-dataset visual generate ./games/star-freight --format trl
|
|
117
|
-
repo-dataset visual inspect ./games/star-freight
|
|
118
|
-
```
|
|
120
|
+
## Star Freight example
|
|
119
121
|
|
|
120
|
-
|
|
122
|
+
Clone the repo for a complete working example: 1,182 records, 28 prompt waves, 5 factions, 7 lanes, 24 constitution rules, and 892 approved assets from a gritty sci-fi RPG.
|
|
121
123
|
|
|
122
124
|
```bash
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# Edit your canon/constitution.md and canon/review-rubric.md
|
|
129
|
-
# Edit your prompt pack, then run the pipeline with --game my-game
|
|
125
|
+
git clone https://github.com/mcp-tool-shop-org/style-dataset-lab
|
|
126
|
+
cd style-dataset-lab
|
|
127
|
+
sdlab bind --stats --project star-freight
|
|
128
|
+
sdlab project doctor --project star-freight
|
|
130
129
|
```
|
|
131
130
|
|
|
132
|
-
##
|
|
131
|
+
## Migrating from v1.x
|
|
133
132
|
|
|
134
|
-
|
|
133
|
+
v2.0 renames `games/` to `projects/` and `--game` to `--project`:
|
|
135
134
|
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
prompts/ Prompt packs per wave (JSON: subjects, variations, defaults)
|
|
140
|
-
references/ IP-Adapter reference images
|
|
141
|
-
control-guides/ ControlNet guide overlays
|
|
142
|
-
identity-packets/ Named character identity spines
|
|
143
|
-
outputs/
|
|
144
|
-
candidates/ Raw generations (gitignored)
|
|
145
|
-
approved/ Curated approved (gitignored)
|
|
146
|
-
rejected/ Curated rejected (gitignored)
|
|
147
|
-
borderline/ Curated borderline (gitignored)
|
|
148
|
-
painterly/ Painterly-style variants (gitignored)
|
|
149
|
-
records/ Per-asset JSON (provenance + judgment + canon binding)
|
|
150
|
-
comparisons/ A-vs-B preference judgments
|
|
151
|
-
exports/ repo-dataset output (gitignored)
|
|
152
|
-
```
|
|
135
|
+
```bash
|
|
136
|
+
# Rename your data directory
|
|
137
|
+
mv games projects
|
|
153
138
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
checkpoint: dreamshaperXL_v21TurboDPMSDE.safetensors
|
|
158
|
-
lora: classipeintxl_v21.safetensors (weight: 1.0)
|
|
159
|
-
resolution: 1024x1024
|
|
160
|
-
steps: 8
|
|
161
|
-
cfg: 2.0
|
|
162
|
-
sampler: dpmpp_sde
|
|
163
|
-
scheduler: karras
|
|
164
|
-
speed: ~9s per image (RTX 5080)
|
|
139
|
+
# --game still works with a deprecation warning (removed in v3.0)
|
|
140
|
+
sdlab bind --game star-freight # works, prints warning
|
|
141
|
+
sdlab bind --project star-freight # canonical form
|
|
165
142
|
```
|
|
166
143
|
|
|
167
|
-
|
|
144
|
+
## Security model
|
|
145
|
+
|
|
146
|
+
**Local-only.** Talks to ComfyUI on `localhost:8188`. No telemetry, no analytics, no external requests. Images stay on your GPU and filesystem.
|
|
168
147
|
|
|
169
148
|
## Requirements
|
|
170
149
|
|
|
171
150
|
- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) running on localhost:8188
|
|
172
|
-
-
|
|
151
|
+
- SDXL-compatible checkpoint + style LoRA
|
|
173
152
|
- Node.js 20+
|
|
174
153
|
- [`@mcptoolshop/repo-dataset`](https://github.com/mcp-tool-shop-org/repo-dataset) for training export
|
|
175
154
|
|
package/bin/sdlab.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* sdlab — CLI entry point for Style Dataset Lab.
|
|
5
|
+
*
|
|
6
|
+
* Thin dispatcher that routes subcommands to existing scripts.
|
|
7
|
+
* Each script exports run(argv) and is lazy-loaded on demand.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const COMMANDS = {
|
|
11
|
+
'init': '../scripts/init.js',
|
|
12
|
+
'generate': '../scripts/generate.js',
|
|
13
|
+
'generate:identity': '../scripts/generate-identity.js',
|
|
14
|
+
'generate:controlnet': '../scripts/generate-controlnet.js',
|
|
15
|
+
'generate:ipadapter': '../scripts/generate-ipadapter.js',
|
|
16
|
+
'curate': '../scripts/curate.js',
|
|
17
|
+
'compare': '../scripts/compare.js',
|
|
18
|
+
'bind': '../scripts/canon-bind.js',
|
|
19
|
+
'canon-bind': '../scripts/canon-bind.js',
|
|
20
|
+
'painterly': '../scripts/painterly.js',
|
|
21
|
+
'painterly:test': '../scripts/painterly-test.js',
|
|
22
|
+
'migrate': '../scripts/migrate-records.js',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Two-word commands under "project" namespace
|
|
26
|
+
const PROJECT_COMMANDS = {
|
|
27
|
+
'doctor': '../scripts/doctor.js',
|
|
28
|
+
'migrate': '../scripts/migrate-records.js',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function printHelp() {
|
|
32
|
+
console.log(`\x1b[1msdlab\x1b[0m — Style Dataset Lab CLI\n`);
|
|
33
|
+
console.log('Usage: sdlab <command> [options]\n');
|
|
34
|
+
console.log('Project:');
|
|
35
|
+
console.log(' init <name> Scaffold a new project from a domain template');
|
|
36
|
+
console.log(' project doctor Validate project config and structure');
|
|
37
|
+
console.log(' project migrate Migrate record schemas');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Pipeline:');
|
|
40
|
+
console.log(' generate Generate candidates from a prompt pack');
|
|
41
|
+
console.log(' generate:identity Generate named-subject identity images');
|
|
42
|
+
console.log(' generate:controlnet ControlNet-guided structural generation');
|
|
43
|
+
console.log(' generate:ipadapter IP-Adapter reference-guided generation');
|
|
44
|
+
console.log(' curate Move candidate to approved/rejected/borderline');
|
|
45
|
+
console.log(' compare Record pairwise A-vs-B comparison');
|
|
46
|
+
console.log(' bind Bind approved records to constitution rules');
|
|
47
|
+
console.log(' painterly Post-processing painterly style pass');
|
|
48
|
+
console.log(' painterly:test Test denoise levels on reference images');
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log('Options:');
|
|
51
|
+
console.log(' --project <name> Project to operate on (default: star-freight)');
|
|
52
|
+
console.log(' --game <name> Deprecated alias for --project');
|
|
53
|
+
console.log(' --help Show this help');
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log('Examples:');
|
|
56
|
+
console.log(' sdlab init my-project --domain character-design');
|
|
57
|
+
console.log(' sdlab generate inputs/prompts/wave1.json --project star-freight');
|
|
58
|
+
console.log(' sdlab bind --stats --project star-freight');
|
|
59
|
+
console.log(' sdlab project doctor --project star-freight');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
const args = process.argv.slice(2);
|
|
64
|
+
|
|
65
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
66
|
+
printHelp();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let command = args[0];
|
|
71
|
+
let commandArgs = args.slice(1);
|
|
72
|
+
|
|
73
|
+
// Handle "project <subcommand>" two-word form
|
|
74
|
+
if (command === 'project' && args[1] && PROJECT_COMMANDS[args[1]]) {
|
|
75
|
+
commandArgs = args.slice(2);
|
|
76
|
+
const modulePath = PROJECT_COMMANDS[args[1]];
|
|
77
|
+
try {
|
|
78
|
+
const mod = await import(modulePath);
|
|
79
|
+
await mod.run(commandArgs);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(`\x1b[31mError:\x1b[0m ${err.message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const modulePath = COMMANDS[command];
|
|
88
|
+
if (!modulePath) {
|
|
89
|
+
console.error(`Unknown command: ${command}`);
|
|
90
|
+
console.error(`Run "sdlab --help" for available commands.`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const mod = await import(modulePath);
|
|
96
|
+
await mod.run(commandArgs);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`\x1b[31mError:\x1b[0m ${err.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
main();
|
package/lib/args.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified CLI argument parser.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* --flag value (space-separated)
|
|
6
|
+
* --flag=value (equals form)
|
|
7
|
+
* --boolean-flag (boolean)
|
|
8
|
+
* positional args (collected in order)
|
|
9
|
+
* deprecated aliases (warns once, maps to canonical name)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { warnDeprecated } from './deprecation.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ArgSpec
|
|
16
|
+
* @property {Object<string, {type: 'string'|'boolean', default?: any, alias?: string}>} flags
|
|
17
|
+
* @property {string[]} [positionals] - names for positional args (in order)
|
|
18
|
+
* @property {Object<string, string>} [deprecated] - old flag name → new flag name
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse CLI arguments.
|
|
23
|
+
* @param {string[]} argv — typically process.argv.slice(2)
|
|
24
|
+
* @param {ArgSpec} spec
|
|
25
|
+
* @returns {{ flags: Object, positionals: string[], rest: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
export function parseArgs(argv, spec = {}) {
|
|
28
|
+
const flagDefs = spec.flags || {};
|
|
29
|
+
const deprecated = spec.deprecated || {};
|
|
30
|
+
const positionalNames = spec.positionals || [];
|
|
31
|
+
|
|
32
|
+
const flags = {};
|
|
33
|
+
const positionals = [];
|
|
34
|
+
|
|
35
|
+
// Set defaults
|
|
36
|
+
for (const [name, def] of Object.entries(flagDefs)) {
|
|
37
|
+
if (def.default !== undefined) flags[name] = def.default;
|
|
38
|
+
else if (def.type === 'boolean') flags[name] = false;
|
|
39
|
+
else flags[name] = undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let i = 0;
|
|
43
|
+
while (i < argv.length) {
|
|
44
|
+
const arg = argv[i];
|
|
45
|
+
|
|
46
|
+
if (arg.startsWith('--')) {
|
|
47
|
+
let key, value;
|
|
48
|
+
|
|
49
|
+
if (arg.includes('=')) {
|
|
50
|
+
const eqIdx = arg.indexOf('=');
|
|
51
|
+
key = arg.slice(2, eqIdx);
|
|
52
|
+
value = arg.slice(eqIdx + 1);
|
|
53
|
+
} else {
|
|
54
|
+
key = arg.slice(2);
|
|
55
|
+
value = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle deprecated aliases
|
|
59
|
+
if (deprecated[key]) {
|
|
60
|
+
warnDeprecated(key, deprecated[key]);
|
|
61
|
+
key = deprecated[key];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle short aliases defined in flagDefs
|
|
65
|
+
const canonical = Object.entries(flagDefs).find(([, def]) => def.alias === key);
|
|
66
|
+
if (canonical) key = canonical[0];
|
|
67
|
+
|
|
68
|
+
// Normalize kebab-case to camelCase for lookup
|
|
69
|
+
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
70
|
+
const def = flagDefs[key] || flagDefs[camelKey];
|
|
71
|
+
|
|
72
|
+
if (def?.type === 'boolean') {
|
|
73
|
+
flags[key in flagDefs ? key : camelKey] = true;
|
|
74
|
+
} else if (value !== undefined) {
|
|
75
|
+
flags[key in flagDefs ? key : camelKey] = value;
|
|
76
|
+
} else if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
|
|
77
|
+
flags[key in flagDefs ? key : camelKey] = argv[++i];
|
|
78
|
+
} else {
|
|
79
|
+
flags[key in flagDefs ? key : camelKey] = true;
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
positionals.push(arg);
|
|
83
|
+
}
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Map positionals to named keys
|
|
88
|
+
const named = {};
|
|
89
|
+
for (let j = 0; j < positionalNames.length && j < positionals.length; j++) {
|
|
90
|
+
named[positionalNames[j]] = positionals[j];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { flags, positionals, named, rest: positionals.slice(positionalNames.length) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extract the --project (or deprecated --game) value from argv.
|
|
98
|
+
* Convenience for scripts that just need the project name.
|
|
99
|
+
*/
|
|
100
|
+
export function getProjectName(argv) {
|
|
101
|
+
const { flags } = parseArgs(argv, {
|
|
102
|
+
flags: { project: { type: 'string', default: 'star-freight' } },
|
|
103
|
+
deprecated: { game: 'project' },
|
|
104
|
+
});
|
|
105
|
+
return flags.project;
|
|
106
|
+
}
|
package/lib/comfyui.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComfyUI HTTP API client — extracted from 5 generation scripts.
|
|
3
|
+
*
|
|
4
|
+
* All functions take comfyUrl as a parameter so projects can override
|
|
5
|
+
* the default localhost:8188 endpoint.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_URL = 'http://127.0.0.1:8188';
|
|
11
|
+
const MAX_POLL_MS = 600_000; // 10 minutes
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if ComfyUI is reachable.
|
|
15
|
+
* @param {string} [comfyUrl]
|
|
16
|
+
* @returns {Promise<boolean>}
|
|
17
|
+
*/
|
|
18
|
+
export async function comfyHealth(comfyUrl = DEFAULT_URL) {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${comfyUrl}/system_stats`);
|
|
21
|
+
return res.ok;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Submit a workflow and poll until complete.
|
|
29
|
+
* @param {Object} workflow — ComfyUI prompt graph
|
|
30
|
+
* @param {string} [comfyUrl]
|
|
31
|
+
* @param {{ clientPrefix?: string, timeoutMs?: number }} [opts]
|
|
32
|
+
* @returns {Promise<Object>} — history entry
|
|
33
|
+
*/
|
|
34
|
+
export async function submitAndWait(workflow, comfyUrl = DEFAULT_URL, opts = {}) {
|
|
35
|
+
const { clientPrefix = 'sdl', timeoutMs = MAX_POLL_MS } = opts;
|
|
36
|
+
const clientId = `${clientPrefix}-${Date.now()}`;
|
|
37
|
+
|
|
38
|
+
const res = await fetch(`${comfyUrl}/prompt`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({ prompt: workflow, client_id: clientId }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const text = await res.text();
|
|
46
|
+
throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { prompt_id: promptId } = await res.json();
|
|
50
|
+
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
while (true) {
|
|
53
|
+
if (Date.now() - start > timeoutMs) {
|
|
54
|
+
throw new Error(`ComfyUI poll timeout after ${timeoutMs / 1000}s for prompt ${promptId}`);
|
|
55
|
+
}
|
|
56
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
57
|
+
const histRes = await fetch(`${comfyUrl}/history/${promptId}`);
|
|
58
|
+
if (!histRes.ok) continue;
|
|
59
|
+
const history = await histRes.json();
|
|
60
|
+
const entry = history[promptId];
|
|
61
|
+
if (!entry) continue;
|
|
62
|
+
if (entry.status?.completed) return entry;
|
|
63
|
+
if (entry.status?.status_str === 'error') {
|
|
64
|
+
throw new Error(`ComfyUI generation failed: ${JSON.stringify(entry.status)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Download a generated image from ComfyUI.
|
|
71
|
+
* @param {string} filename
|
|
72
|
+
* @param {string} [subfolder]
|
|
73
|
+
* @param {string} [comfyUrl]
|
|
74
|
+
* @param {string} [type='output']
|
|
75
|
+
* @returns {Promise<Buffer>}
|
|
76
|
+
*/
|
|
77
|
+
export async function downloadImage(filename, subfolder, comfyUrl = DEFAULT_URL, type = 'output') {
|
|
78
|
+
const url = `${comfyUrl}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || '')}&type=${type}`;
|
|
79
|
+
const res = await fetch(url);
|
|
80
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
81
|
+
return Buffer.from(await res.arrayBuffer());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Upload an image to ComfyUI's input folder.
|
|
86
|
+
* @param {string} filePath — local file to upload
|
|
87
|
+
* @param {string} filename — name to assign in ComfyUI
|
|
88
|
+
* @param {string} [comfyUrl]
|
|
89
|
+
* @returns {Promise<string>} — assigned name from ComfyUI
|
|
90
|
+
*/
|
|
91
|
+
export async function uploadImage(filePath, filename, comfyUrl = DEFAULT_URL) {
|
|
92
|
+
const data = await readFile(filePath);
|
|
93
|
+
const formData = new FormData();
|
|
94
|
+
formData.append('image', new Blob([data], { type: 'image/png' }), filename);
|
|
95
|
+
formData.append('overwrite', 'true');
|
|
96
|
+
|
|
97
|
+
const res = await fetch(`${comfyUrl}/upload/image`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
body: formData,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
const text = await res.text();
|
|
104
|
+
throw new Error(`Upload failed: ${res.status} ${text}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = await res.json();
|
|
108
|
+
return result.name;
|
|
109
|
+
}
|