@mcptoolshop/style-dataset-lab 3.0.0 → 3.1.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 +108 -15
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +71 -0
- package/README.es.md +99 -8
- package/README.fr.md +100 -9
- package/README.hi.md +102 -9
- package/README.it.md +99 -8
- package/README.ja.md +100 -9
- package/README.md +64 -1
- package/README.pt-BR.md +99 -8
- package/README.zh.md +100 -9
- package/SECURITY.md +6 -5
- package/bin/sdlab.js +289 -62
- package/lib/adapters/comfyui-runner.js +11 -15
- 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 +7 -3
- package/lib/batch-runs.js +162 -31
- package/lib/batch-sheet-render.js +5 -2
- package/lib/brief-compiler.js +38 -11
- package/lib/card.js +11 -4
- package/lib/comfyui-output.js +70 -0
- package/lib/comfyui.js +100 -6
- package/lib/config.js +68 -3
- 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/implementation-packs.js +15 -1
- package/lib/log.js +41 -4
- package/lib/paths.js +41 -3
- package/lib/refine-briefs.js +16 -9
- package/lib/reingest-selected.js +14 -3
- package/lib/reingest.js +15 -1
- package/lib/run-summary.js +3 -2
- package/lib/runtime-runs.js +59 -9
- package/lib/selections.js +54 -2
- package/lib/snapshot.js +100 -7
- package/lib/split.js +175 -17
- package/lib/training-adapters.js +79 -0
- package/lib/training-manifests.js +17 -3
- package/lib/training-packages.js +29 -28
- package/lib/training-profiles.js +10 -0
- package/package.json +27 -3
- package/scripts/batch-generate.js +72 -20
- package/scripts/batch-sheet.js +3 -3
- package/scripts/batch-show.js +3 -3
- package/scripts/brief-compile.js +8 -8
- package/scripts/brief-show.js +4 -3
- package/scripts/canon-bind.js +7 -9
- package/scripts/card.js +22 -25
- package/scripts/compare.js +38 -33
- package/scripts/critique-show.js +3 -3
- package/scripts/critique.js +3 -3
- package/scripts/curate.js +91 -46
- package/scripts/doctor.js +3 -5
- 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 +96 -29
- package/scripts/implementation-pack.js +19 -14
- package/scripts/init.js +115 -17
- package/scripts/painterly.js +8 -10
- package/scripts/refine.js +3 -3
- package/scripts/reingest-selected.js +3 -3
- package/scripts/reingest.js +33 -20
- package/scripts/run-generate.js +4 -4
- package/scripts/run-list.js +3 -3
- package/scripts/run-show.js +3 -3
- package/scripts/select.js +5 -4
- package/scripts/selection-show.js +3 -3
- 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 +2 -2
- package/scripts/workflow-show.js +4 -3
- package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/architecture/terminology.json +10 -8
- package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/character-design/project.json +4 -2
- 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/game-art/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/game-art/project.json +4 -2
- package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/vehicle-mech/project.json +4 -2
- 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/scripts/eval-run.js
CHANGED
|
@@ -10,40 +10,42 @@
|
|
|
10
10
|
* sdlab eval-run list [--project <name>]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { readFile } from 'node:fs/promises';
|
|
14
13
|
import { join } from 'node:path';
|
|
15
|
-
import { getProjectName } from '../lib/args.js';
|
|
16
|
-
import { REPO_ROOT } from '../lib/paths.js';
|
|
14
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
15
|
+
import { REPO_ROOT, resolveSafeProjectPath } from '../lib/paths.js';
|
|
16
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
17
17
|
import { listTrainingManifests } from '../lib/training-manifests.js';
|
|
18
18
|
import { listEvalPacks } from '../lib/eval-pack.js';
|
|
19
19
|
import { createEvalRun, scoreEvalRun, loadEvalRun, listEvalRuns } from '../lib/eval-runs.js';
|
|
20
20
|
|
|
21
21
|
export async function run(argv = process.argv.slice(2)) {
|
|
22
|
-
const
|
|
22
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
23
|
+
flags: {
|
|
24
|
+
project: { type: 'string' },
|
|
25
|
+
manifest: { type: 'string' },
|
|
26
|
+
'eval-pack': { type: 'string' },
|
|
27
|
+
outputs: { type: 'string' },
|
|
28
|
+
},
|
|
29
|
+
deprecated: { game: 'project' },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const projectName = flags.project || getProjectName(argv);
|
|
23
33
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
24
|
-
|
|
25
|
-
const
|
|
34
|
+
// First non-flag positional that isn't an er-id is the subcommand
|
|
35
|
+
const subcommand = positionals.find(a => !a.startsWith('er-')) || 'list';
|
|
26
36
|
|
|
27
37
|
if (subcommand === 'create') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const manifestIdx = args.indexOf('--manifest');
|
|
31
|
-
if (manifestIdx >= 0) {
|
|
32
|
-
manifestId = args[manifestIdx + 1];
|
|
33
|
-
} else {
|
|
38
|
+
let manifestId = flags.manifest;
|
|
39
|
+
if (!manifestId) {
|
|
34
40
|
const manifests = await listTrainingManifests(projectRoot);
|
|
35
|
-
if (manifests.length === 0) throw
|
|
41
|
+
if (manifests.length === 0) throw inputError('INPUT_NO_MANIFEST', 'No training manifests. Run: sdlab training-manifest create');
|
|
36
42
|
manifestId = manifests[manifests.length - 1].id;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const evalIdx = args.indexOf('--eval-pack');
|
|
42
|
-
if (evalIdx >= 0) {
|
|
43
|
-
evalPackId = args[evalIdx + 1];
|
|
44
|
-
} else {
|
|
45
|
+
let evalPackId = flags['eval-pack'] || flags.evalPack;
|
|
46
|
+
if (!evalPackId) {
|
|
45
47
|
const packs = await listEvalPacks(projectRoot);
|
|
46
|
-
if (packs.length === 0) throw
|
|
48
|
+
if (packs.length === 0) throw inputError('INPUT_NO_EVALPACK', 'No eval packs. Run: sdlab eval-pack build');
|
|
47
49
|
evalPackId = packs[packs.length - 1].id;
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -57,12 +59,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
57
59
|
console.log(` Status: created (use "sdlab eval-run score" to score outputs)`);
|
|
58
60
|
|
|
59
61
|
} else if (subcommand === 'score') {
|
|
60
|
-
const evalRunId =
|
|
61
|
-
if (!evalRunId) throw
|
|
62
|
+
const evalRunId = positionals.find(a => a.startsWith('er-'));
|
|
63
|
+
if (!evalRunId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-run score <eval-run-id> --outputs <path>');
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const outputsPath = args[outputsIdx + 1];
|
|
65
|
+
if (!flags.outputs) throw inputError('INPUT_MISSING_FLAG', '--outputs <path> is required (JSONL with record_id, accept, notes)');
|
|
66
|
+
const outputsPath = resolveSafeProjectPath(projectRoot, flags.outputs, { baseRoot: REPO_ROOT, flagName: 'outputs' });
|
|
66
67
|
|
|
67
68
|
console.log(`\x1b[1msdlab eval-run score\x1b[0m — ${evalRunId}\n`);
|
|
68
69
|
|
|
@@ -80,8 +81,8 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
} else if (subcommand === 'show') {
|
|
83
|
-
const evalRunId =
|
|
84
|
-
if (!evalRunId) throw
|
|
84
|
+
const evalRunId = positionals.find(a => a.startsWith('er-'));
|
|
85
|
+
if (!evalRunId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-run show <eval-run-id>');
|
|
85
86
|
|
|
86
87
|
const run = await loadEvalRun(projectRoot, evalRunId);
|
|
87
88
|
console.log(`\x1b[1mEval run\x1b[0m — ${evalRunId}\n`);
|
|
@@ -112,10 +113,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
} else {
|
|
115
|
-
throw
|
|
116
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: create, score, show, list`);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
if (process.argv[1] && (process.argv[1].endsWith('eval-run.js') || process.argv[1].endsWith('eval-run'))) {
|
|
120
|
-
run().catch(
|
|
121
|
+
run().catch(handleCliError);
|
|
121
122
|
}
|
package/scripts/export.js
CHANGED
|
@@ -9,53 +9,52 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
-
import { getProjectName } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
14
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
14
15
|
import { listSnapshots } from '../lib/snapshot.js';
|
|
15
16
|
import { listSplits } from '../lib/split.js';
|
|
16
|
-
import { buildExport, listExports
|
|
17
|
+
import { buildExport, listExports } from '../lib/export.js';
|
|
17
18
|
|
|
18
19
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
-
const
|
|
20
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
21
|
+
flags: {
|
|
22
|
+
project: { type: 'string' },
|
|
23
|
+
snapshot: { type: 'string' },
|
|
24
|
+
split: { type: 'string' },
|
|
25
|
+
profile: { type: 'string' },
|
|
26
|
+
copy: { type: 'boolean', default: false },
|
|
27
|
+
},
|
|
28
|
+
deprecated: { game: 'project' },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const projectName = flags.project || getProjectName(argv);
|
|
20
32
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
21
|
-
const subcommand =
|
|
22
|
-
const args = argv.filter(a => a !== subcommand);
|
|
33
|
+
const subcommand = positionals[0] || 'list';
|
|
23
34
|
|
|
24
35
|
if (subcommand === 'build') {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const snapIdx = args.indexOf('--snapshot');
|
|
28
|
-
if (snapIdx >= 0) {
|
|
29
|
-
snapshotId = args[snapIdx + 1];
|
|
30
|
-
} else {
|
|
36
|
+
let snapshotId = flags.snapshot;
|
|
37
|
+
if (!snapshotId) {
|
|
31
38
|
const snapshots = await listSnapshots(projectRoot);
|
|
32
|
-
if (snapshots.length === 0) throw
|
|
39
|
+
if (snapshots.length === 0) throw inputError('INPUT_NO_SNAPSHOT', 'No snapshots found. Run: sdlab snapshot create');
|
|
33
40
|
snapshotId = snapshots[snapshots.length - 1].id;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const splitIdx = args.indexOf('--split');
|
|
39
|
-
if (splitIdx >= 0) {
|
|
40
|
-
splitId = args[splitIdx + 1];
|
|
41
|
-
} else {
|
|
43
|
+
let splitId = flags.split;
|
|
44
|
+
if (!splitId) {
|
|
42
45
|
const splits = await listSplits(projectRoot);
|
|
43
|
-
if (splits.length === 0) throw
|
|
46
|
+
if (splits.length === 0) throw inputError('INPUT_NO_SPLIT', 'No splits found. Run: sdlab split build');
|
|
44
47
|
splitId = splits[splits.length - 1].id;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
const profileIdx = args.indexOf('--profile');
|
|
48
|
-
const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
|
|
49
|
-
const copy = args.includes('--copy');
|
|
50
|
-
|
|
51
50
|
console.log(`\x1b[1msdlab export build\x1b[0m — ${projectName}`);
|
|
52
51
|
console.log(` Snapshot: ${snapshotId}`);
|
|
53
52
|
console.log(` Split: ${splitId}`);
|
|
54
|
-
console.log(` Profile: ${
|
|
55
|
-
console.log(` Images: ${copy ? 'copy' : 'symlink'}`);
|
|
53
|
+
console.log(` Profile: ${flags.profile || 'canonical-package (built-in)'}`);
|
|
54
|
+
console.log(` Images: ${flags.copy ? 'copy' : 'symlink'}`);
|
|
56
55
|
console.log('');
|
|
57
56
|
|
|
58
|
-
const result = await buildExport(projectRoot, snapshotId, splitId, { profileId, copy });
|
|
57
|
+
const result = await buildExport(projectRoot, snapshotId, splitId, { profileId: flags.profile, copy: flags.copy });
|
|
59
58
|
|
|
60
59
|
console.log(` \x1b[32m✓\x1b[0m Export: ${result.exportId}`);
|
|
61
60
|
console.log(` Records: ${result.recordCount}`);
|
|
@@ -73,13 +72,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
} else {
|
|
76
|
-
throw
|
|
75
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, list`);
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
if (process.argv[1] && (process.argv[1].endsWith('export.js') || process.argv[1].endsWith('export'))) {
|
|
81
|
-
run().catch(
|
|
82
|
-
console.error(err.message || err);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
});
|
|
80
|
+
run().catch(handleCliError);
|
|
85
81
|
}
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* sdlab generate:controlnet --subject naia --guide <path> --seeds 4 --project star-freight
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
-
import { getProjectName } from "../lib/args.js";
|
|
18
|
-
import { REPO_ROOT } from "../lib/paths.js";
|
|
17
|
+
import { getProjectName, parseNumberFlag } from "../lib/args.js";
|
|
18
|
+
import { REPO_ROOT, resolveSafeProjectPath } from "../lib/paths.js";
|
|
19
|
+
import { readJsonFile } from "../lib/config.js";
|
|
20
|
+
import { inputError, runtimeError, handleCliError } from "../lib/errors.js";
|
|
19
21
|
import { comfyHealth, submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
|
|
20
22
|
|
|
21
23
|
const DEFAULTS = {
|
|
@@ -33,7 +35,12 @@ const DEFAULTS = {
|
|
|
33
35
|
function parseLocalArgs(argv) {
|
|
34
36
|
const get = (flag, def) => {
|
|
35
37
|
const i = argv.indexOf(flag);
|
|
36
|
-
|
|
38
|
+
if (i < 0 || i + 1 >= argv.length) return def;
|
|
39
|
+
const v = argv[i + 1];
|
|
40
|
+
if (typeof v === 'string' && v.startsWith('--')) {
|
|
41
|
+
throw inputError('INPUT_MISSING_VALUE', `Flag ${flag} is missing its value (got another flag: ${v}).`);
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
37
44
|
};
|
|
38
45
|
return {
|
|
39
46
|
subject: get("--subject", "unknown"),
|
|
@@ -41,9 +48,9 @@ function parseLocalArgs(argv) {
|
|
|
41
48
|
prompt: get("--prompt", null),
|
|
42
49
|
promptFile: get("--prompt-file", null),
|
|
43
50
|
negative: get("--negative", null),
|
|
44
|
-
seeds:
|
|
45
|
-
weight:
|
|
46
|
-
guidanceEnd:
|
|
51
|
+
seeds: parseNumberFlag('seeds', get("--seeds", "4"), { int: true, min: 1 }),
|
|
52
|
+
weight: parseNumberFlag('weight', get("--weight", "0.70"), { min: 0, max: 2 }),
|
|
53
|
+
guidanceEnd: parseNumberFlag('guidance-end', get("--guidance-end", "0.65"), { min: 0, max: 1 }),
|
|
47
54
|
controlModel: get("--model", "controlnet-canny-sdxl-1.0.safetensors"),
|
|
48
55
|
dryRun: argv.includes("--dry-run"),
|
|
49
56
|
};
|
|
@@ -167,21 +174,25 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
167
174
|
const opts = parseLocalArgs(argv);
|
|
168
175
|
|
|
169
176
|
if (!opts.guidePath) {
|
|
170
|
-
throw
|
|
177
|
+
throw inputError('INPUT_MISSING_FLAG', "--guide <path> is required");
|
|
171
178
|
}
|
|
172
179
|
|
|
180
|
+
// Validate guide stays inside the project tree.
|
|
181
|
+
const safeGuide = resolveSafeProjectPath(GAME_ROOT, opts.guidePath, { flagName: 'guide' });
|
|
182
|
+
|
|
173
183
|
// Resolve prompt
|
|
174
184
|
let prompt = opts.prompt;
|
|
175
185
|
let negative = opts.negative;
|
|
176
186
|
|
|
177
187
|
if (opts.promptFile && !prompt) {
|
|
178
|
-
const
|
|
188
|
+
const safePromptFile = resolveSafeProjectPath(GAME_ROOT, opts.promptFile, { flagName: 'prompt-file' });
|
|
189
|
+
const pf = await readJsonFile(safePromptFile, { requiredFields: ['prompt'] });
|
|
179
190
|
prompt = pf.prompt;
|
|
180
191
|
negative = negative || pf.negative;
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
if (!prompt) {
|
|
184
|
-
throw
|
|
195
|
+
throw inputError('INPUT_MISSING_FLAG', "--prompt or --prompt-file required");
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
if (!negative) {
|
|
@@ -200,12 +211,12 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
200
211
|
if (!opts.dryRun) {
|
|
201
212
|
const online = await comfyHealth(COMFY_URL);
|
|
202
213
|
if (!online) {
|
|
203
|
-
throw
|
|
214
|
+
throw runtimeError('RUNTIME_COMFY_UNREACHABLE', "ComfyUI not reachable at " + COMFY_URL);
|
|
204
215
|
}
|
|
205
216
|
console.log("\x1b[32m+\x1b[0m ComfyUI online");
|
|
206
217
|
}
|
|
207
218
|
|
|
208
|
-
const guideFullPath =
|
|
219
|
+
const guideFullPath = safeGuide;
|
|
209
220
|
const guideFilename = `cn_guide_${opts.subject}.png`;
|
|
210
221
|
|
|
211
222
|
if (!opts.dryRun) {
|
|
@@ -215,6 +226,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
215
226
|
|
|
216
227
|
await mkdir(join(GAME_ROOT, "outputs/candidates"), { recursive: true });
|
|
217
228
|
|
|
229
|
+
let successes = 0, failures = 0;
|
|
218
230
|
for (let si = 0; si < opts.seeds; si++) {
|
|
219
231
|
const seed = DEFAULTS.base_seed + si;
|
|
220
232
|
const assetId = `${opts.subject}_cn_s${si}`;
|
|
@@ -254,19 +266,21 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
254
266
|
const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
|
|
255
267
|
await writeFile(join(GAME_ROOT, destPath), imgData);
|
|
256
268
|
console.log(` \x1b[32m+\x1b[0m ${destPath} (${elapsed}ms, ${imgData.length} bytes)`);
|
|
269
|
+
successes++;
|
|
257
270
|
} catch (err) {
|
|
258
271
|
console.log(` \x1b[31mx\x1b[0m ${err.message}`);
|
|
272
|
+
failures++;
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
console.log(
|
|
276
|
+
console.log(`\n\x1b[32m+\x1b[0m ControlNet discovery complete (${successes} success, ${failures} failed)`);
|
|
263
277
|
if (opts.dryRun) console.log(" (dry run)");
|
|
278
|
+
if (!opts.dryRun && failures > 0 && successes === 0) {
|
|
279
|
+
throw runtimeError('RUNTIME_ALL_FAILED', `All ${opts.seeds} ControlNet generation attempts failed.`);
|
|
280
|
+
}
|
|
264
281
|
}
|
|
265
282
|
|
|
266
283
|
// Direct execution guard
|
|
267
284
|
if (process.argv[1] && (process.argv[1].endsWith('generate-controlnet.js') || process.argv[1].endsWith('generate-controlnet'))) {
|
|
268
|
-
run().catch(
|
|
269
|
-
console.error(err.message || err);
|
|
270
|
-
process.exit(1);
|
|
271
|
-
});
|
|
285
|
+
run().catch(handleCliError);
|
|
272
286
|
}
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
* --denoise N Denoise strength for follow_on phase (default: 0.38)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import { getProjectName } from "../lib/args.js";
|
|
26
|
-
import { REPO_ROOT } from "../lib/paths.js";
|
|
25
|
+
import { getProjectName, parseNumberFlag } from "../lib/args.js";
|
|
26
|
+
import { REPO_ROOT, resolveSafeProjectPath } from "../lib/paths.js";
|
|
27
|
+
import { readJsonFile } from "../lib/config.js";
|
|
28
|
+
import { inputError, runtimeError, handleCliError } from "../lib/errors.js";
|
|
27
29
|
import { comfyHealth, submitAndWait, downloadImage } from "../lib/comfyui.js";
|
|
28
30
|
|
|
29
31
|
// ── Defaults (match existing lab pipeline) ──
|
|
@@ -378,13 +380,24 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
378
380
|
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
379
381
|
const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
|
|
380
382
|
|
|
381
|
-
|
|
383
|
+
// Collect positionals, skipping values that belong to known flags
|
|
384
|
+
const knownFlagsWithValue = new Set(['--subject', '--seeds', '--phase', '--anchor', '--denoise', '--project', '--game']);
|
|
385
|
+
const positionals = [];
|
|
386
|
+
for (let k = 0; k < argv.length; k++) {
|
|
387
|
+
const a = argv[k];
|
|
388
|
+
if (a.startsWith('--')) {
|
|
389
|
+
if (!a.includes('=') && knownFlagsWithValue.has(a)) k++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
positionals.push(a);
|
|
393
|
+
}
|
|
394
|
+
const packPath = positionals[0];
|
|
382
395
|
const dryRun = argv.includes("--dry-run");
|
|
383
396
|
const subjectFilter = argv.includes("--subject")
|
|
384
397
|
? argv[argv.indexOf("--subject") + 1]
|
|
385
398
|
: null;
|
|
386
399
|
const seedCount = argv.includes("--seeds")
|
|
387
|
-
?
|
|
400
|
+
? parseNumberFlag('seeds', argv[argv.indexOf("--seeds") + 1], { int: true, min: 1 })
|
|
388
401
|
: 3;
|
|
389
402
|
const phase = argv.includes("--phase")
|
|
390
403
|
? argv[argv.indexOf("--phase") + 1]
|
|
@@ -393,30 +406,31 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
393
406
|
? argv[argv.indexOf("--anchor") + 1]
|
|
394
407
|
: null;
|
|
395
408
|
const denoise = argv.includes("--denoise")
|
|
396
|
-
?
|
|
409
|
+
? parseNumberFlag('denoise', argv[argv.indexOf("--denoise") + 1], { min: 0, max: 1 })
|
|
397
410
|
: 0.38;
|
|
398
411
|
|
|
399
412
|
if (!packPath) {
|
|
400
|
-
throw
|
|
413
|
+
throw inputError('INPUT_MISSING_ARGS', "Usage: sdlab generate:identity <packet-file> [options]");
|
|
401
414
|
}
|
|
402
415
|
|
|
403
416
|
if (phase === "follow_on" && !anchorPath) {
|
|
404
|
-
throw
|
|
417
|
+
throw inputError('INPUT_MISSING_FLAG', "--phase follow_on requires --anchor <image-path>");
|
|
405
418
|
}
|
|
406
419
|
|
|
407
|
-
const fullPackPath =
|
|
408
|
-
|
|
420
|
+
const fullPackPath = resolveSafeProjectPath(GAME_ROOT, packPath, { flagName: 'packet-file' });
|
|
421
|
+
if (anchorPath) resolveSafeProjectPath(GAME_ROOT, anchorPath, { flagName: 'anchor' });
|
|
422
|
+
const pack = await readJsonFile(fullPackPath, { requiredFields: ['subjects'] });
|
|
409
423
|
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
412
|
-
throw
|
|
424
|
+
const vErrors = validatePacket(pack);
|
|
425
|
+
if (vErrors.length > 0) {
|
|
426
|
+
throw inputError('INPUT_BAD_PACKET', "Packet validation failed:\n " + vErrors.join("\n "));
|
|
413
427
|
}
|
|
414
428
|
|
|
415
429
|
let subjects = pack.subjects;
|
|
416
430
|
if (subjectFilter) {
|
|
417
431
|
subjects = subjects.filter((s) => s.subject_name === subjectFilter);
|
|
418
432
|
if (subjects.length === 0) {
|
|
419
|
-
throw
|
|
433
|
+
throw inputError('INPUT_UNKNOWN_SUBJECT', `No subject found with name '${subjectFilter}'`);
|
|
420
434
|
}
|
|
421
435
|
}
|
|
422
436
|
|
|
@@ -439,7 +453,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
439
453
|
if (!dryRun) {
|
|
440
454
|
const online = await comfyHealth(COMFY_URL);
|
|
441
455
|
if (!online) {
|
|
442
|
-
throw
|
|
456
|
+
throw runtimeError('RUNTIME_COMFY_UNREACHABLE', "ComfyUI not reachable at " + COMFY_URL);
|
|
443
457
|
}
|
|
444
458
|
console.log("\x1b[32m+\x1b[0m ComfyUI online");
|
|
445
459
|
}
|
|
@@ -448,6 +462,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
448
462
|
await mkdir(join(GAME_ROOT, "records"), { recursive: true });
|
|
449
463
|
|
|
450
464
|
let generated = 0;
|
|
465
|
+
let errors = 0;
|
|
451
466
|
let imageIndex = 0;
|
|
452
467
|
|
|
453
468
|
for (const subject of subjects) {
|
|
@@ -532,6 +547,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
532
547
|
console.log(` \x1b[32m+\x1b[0m ${destPath} (${elapsed}ms, ${imgData.length} bytes)`);
|
|
533
548
|
} catch (err) {
|
|
534
549
|
console.log(` \x1b[31mx\x1b[0m ${err.message}`);
|
|
550
|
+
errors++;
|
|
535
551
|
}
|
|
536
552
|
|
|
537
553
|
generated++;
|
|
@@ -540,8 +556,12 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
540
556
|
}
|
|
541
557
|
}
|
|
542
558
|
|
|
543
|
-
|
|
559
|
+
const succeeded = generated - errors;
|
|
560
|
+
console.log(`\n\x1b[32m+\x1b[0m Generated ${succeeded} images (${errors} errors)`);
|
|
544
561
|
if (dryRun) console.log(" (dry run — no images produced)");
|
|
562
|
+
if (!dryRun && errors > 0 && succeeded === 0) {
|
|
563
|
+
throw runtimeError('RUNTIME_ALL_FAILED', `All ${generated} generation attempts failed.`);
|
|
564
|
+
}
|
|
545
565
|
|
|
546
566
|
console.log("\nNext steps:");
|
|
547
567
|
if (phase === "discovery") {
|
|
@@ -558,8 +578,5 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
558
578
|
|
|
559
579
|
// Direct execution guard
|
|
560
580
|
if (process.argv[1] && (process.argv[1].endsWith('generate-identity.js') || process.argv[1].endsWith('generate-identity'))) {
|
|
561
|
-
run().catch(
|
|
562
|
-
console.error(err.message || err);
|
|
563
|
-
process.exit(1);
|
|
564
|
-
});
|
|
581
|
+
run().catch(handleCliError);
|
|
565
582
|
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* sdlab generate:ipadapter --subject corrigan --ref <image> --seeds 4 --project star-freight
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
-
import { getProjectName } from "../lib/args.js";
|
|
17
|
-
import { REPO_ROOT } from "../lib/paths.js";
|
|
16
|
+
import { getProjectName, parseNumberFlag } from "../lib/args.js";
|
|
17
|
+
import { REPO_ROOT, resolveSafeProjectPath } from "../lib/paths.js";
|
|
18
|
+
import { inputError, runtimeError, handleCliError } from "../lib/errors.js";
|
|
18
19
|
import { comfyHealth, submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
|
|
19
20
|
|
|
20
21
|
const DEFAULTS = {
|
|
@@ -34,17 +35,22 @@ const DEFAULTS = {
|
|
|
34
35
|
function parseLocalArgs(argv) {
|
|
35
36
|
const get = (flag, def) => {
|
|
36
37
|
const i = argv.indexOf(flag);
|
|
37
|
-
|
|
38
|
+
if (i < 0 || i + 1 >= argv.length) return def;
|
|
39
|
+
const v = argv[i + 1];
|
|
40
|
+
if (typeof v === 'string' && v.startsWith('--')) {
|
|
41
|
+
throw inputError('INPUT_MISSING_VALUE', `Flag ${flag} is missing its value (got another flag: ${v}).`);
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
38
44
|
};
|
|
39
45
|
return {
|
|
40
46
|
subject: get("--subject", "unknown"),
|
|
41
47
|
refPath: get("--ref", null),
|
|
42
48
|
prompt: get("--prompt", null),
|
|
43
49
|
negative: get("--negative", null),
|
|
44
|
-
seeds:
|
|
45
|
-
weight:
|
|
46
|
-
start:
|
|
47
|
-
end:
|
|
50
|
+
seeds: parseNumberFlag('seeds', get("--seeds", "4"), { int: true, min: 1 }),
|
|
51
|
+
weight: parseNumberFlag('weight', get("--weight", "0.55"), { min: 0, max: 2 }),
|
|
52
|
+
start: parseNumberFlag('start', get("--start", "0.0"), { min: 0, max: 1 }),
|
|
53
|
+
end: parseNumberFlag('end', get("--end", "0.8"), { min: 0, max: 1 }),
|
|
48
54
|
dryRun: argv.includes("--dry-run"),
|
|
49
55
|
};
|
|
50
56
|
}
|
|
@@ -174,12 +180,15 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
174
180
|
const opts = parseLocalArgs(argv);
|
|
175
181
|
|
|
176
182
|
if (!opts.refPath) {
|
|
177
|
-
throw
|
|
183
|
+
throw inputError('INPUT_MISSING_FLAG', "--ref <path> is required");
|
|
178
184
|
}
|
|
179
185
|
if (!opts.prompt) {
|
|
180
|
-
throw
|
|
186
|
+
throw inputError('INPUT_MISSING_FLAG', "--prompt is required");
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
// Validate --ref path stays inside project tree.
|
|
190
|
+
const safeRefPath = resolveSafeProjectPath(GAME_ROOT, opts.refPath, { flagName: 'ref' });
|
|
191
|
+
|
|
183
192
|
const negative = opts.negative ||
|
|
184
193
|
"photorealistic, photograph, 3d render, anime, cartoon, text, watermark, blurry, low quality";
|
|
185
194
|
|
|
@@ -196,11 +205,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
196
205
|
if (!opts.dryRun) {
|
|
197
206
|
const online = await comfyHealth(COMFY_URL);
|
|
198
207
|
if (!online) {
|
|
199
|
-
throw
|
|
208
|
+
throw runtimeError('RUNTIME_COMFY_UNREACHABLE', "ComfyUI not reachable at " + COMFY_URL);
|
|
200
209
|
}
|
|
201
210
|
console.log("\x1b[32m+\x1b[0m ComfyUI online");
|
|
202
211
|
|
|
203
|
-
await uploadImage(
|
|
212
|
+
await uploadImage(safeRefPath, refFilename, COMFY_URL);
|
|
204
213
|
console.log(`\x1b[32m+\x1b[0m Reference uploaded: ${refFilename}`);
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -255,8 +264,5 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
255
264
|
|
|
256
265
|
// Direct execution guard
|
|
257
266
|
if (process.argv[1] && (process.argv[1].endsWith('generate-ipadapter.js') || process.argv[1].endsWith('generate-ipadapter'))) {
|
|
258
|
-
run().catch(
|
|
259
|
-
console.error(err.message || err);
|
|
260
|
-
process.exit(1);
|
|
261
|
-
});
|
|
267
|
+
run().catch(handleCliError);
|
|
262
268
|
}
|