@mcptoolshop/style-dataset-lab 2.2.1 → 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.es.md +76 -144
- package/README.fr.md +75 -143
- package/README.hi.md +75 -143
- package/README.it.md +76 -144
- package/README.ja.md +75 -143
- package/README.md +36 -8
- package/README.pt-BR.md +75 -143
- package/README.zh.md +76 -144
- package/SECURITY.md +3 -1
- package/bin/sdlab.js +146 -17
- 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/errors.js +82 -0
- package/lib/generated-provenance.js +87 -0
- package/lib/log.js +36 -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/snapshot.js +5 -2
- package/lib/split.js +5 -2
- package/lib/workflow-profiles.js +161 -0
- package/package.json +8 -6
- 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/snapshot.js +27 -23
- package/scripts/split.js +3 -1
- 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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* select.js — Approve outputs from runs or batches.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sdlab select --run run_2026-04-15_001 --approve 001.png,003.png
|
|
8
|
+
* sdlab select --batch batch_2026-04-16_001 --approve slot_neutral:001.png,slot_anger:001.png
|
|
9
|
+
* sdlab select --run run_2026-04-15_001 --approve 001.png --reason "best continuity" --tags portrait,keeper
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseArgs } from '../lib/args.js';
|
|
13
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
14
|
+
import {
|
|
15
|
+
createSelectionFromRun,
|
|
16
|
+
createSelectionFromBatch,
|
|
17
|
+
saveSelection,
|
|
18
|
+
} from '../lib/selections.js';
|
|
19
|
+
import { info } from '../lib/log.js';
|
|
20
|
+
|
|
21
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
22
|
+
const { flags } = parseArgs(argv, {
|
|
23
|
+
flags: {
|
|
24
|
+
project: { type: 'string', default: 'star-freight' },
|
|
25
|
+
run: { type: 'string' },
|
|
26
|
+
batch: { type: 'string' },
|
|
27
|
+
approve: { type: 'string' },
|
|
28
|
+
reason: { type: 'string' },
|
|
29
|
+
tags: { type: 'string' },
|
|
30
|
+
json: { type: 'boolean' },
|
|
31
|
+
},
|
|
32
|
+
deprecated: { game: 'project' },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!flags.approve || (!flags.run && !flags.batch)) {
|
|
36
|
+
console.log('Usage: sdlab select --run <id> --approve <files>');
|
|
37
|
+
console.log(' sdlab select --batch <id> --approve <slot:file,...>');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Options:');
|
|
40
|
+
console.log(' --run <id> Source run');
|
|
41
|
+
console.log(' --batch <id> Source batch');
|
|
42
|
+
console.log(' --approve <list> Comma-separated files (run) or slot:file pairs (batch)');
|
|
43
|
+
console.log(' --reason <text> Why these outputs were chosen');
|
|
44
|
+
console.log(' --tags <list> Comma-separated tags');
|
|
45
|
+
console.log(' --json Output manifest as JSON');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const projectName = flags.project;
|
|
50
|
+
const projectRoot = getProjectRoot(projectName);
|
|
51
|
+
const reason = flags.reason || undefined;
|
|
52
|
+
const tags = flags.tags ? flags.tags.split(',').map(t => t.trim()) : undefined;
|
|
53
|
+
|
|
54
|
+
let result;
|
|
55
|
+
|
|
56
|
+
if (flags.run) {
|
|
57
|
+
// Run selection: --approve 001.png,003.png
|
|
58
|
+
const approvedFiles = flags.approve.split(',').map(f => f.trim());
|
|
59
|
+
|
|
60
|
+
result = await createSelectionFromRun({
|
|
61
|
+
projectRoot,
|
|
62
|
+
projectId: projectName,
|
|
63
|
+
runId: flags.run,
|
|
64
|
+
approvedFiles,
|
|
65
|
+
reason,
|
|
66
|
+
tags,
|
|
67
|
+
});
|
|
68
|
+
} else if (flags.batch) {
|
|
69
|
+
// Batch selection: --approve slot_neutral:001.png,slot_anger:001.png
|
|
70
|
+
const pairs = flags.approve.split(',').map(p => p.trim());
|
|
71
|
+
const approvedSlots = pairs.map(pair => {
|
|
72
|
+
const colonIdx = pair.indexOf(':');
|
|
73
|
+
if (colonIdx < 0) {
|
|
74
|
+
throw new Error(`Invalid batch approve format: "${pair}". Use slot_id:filename`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
slotId: pair.slice(0, colonIdx),
|
|
78
|
+
filename: pair.slice(colonIdx + 1),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
result = await createSelectionFromBatch({
|
|
83
|
+
projectRoot,
|
|
84
|
+
projectId: projectName,
|
|
85
|
+
batchId: flags.batch,
|
|
86
|
+
approvedSlots,
|
|
87
|
+
reason,
|
|
88
|
+
tags,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { selectionId, selectionDir, manifest } = result;
|
|
93
|
+
|
|
94
|
+
// Save the selection
|
|
95
|
+
await saveSelection(selectionDir, manifest);
|
|
96
|
+
|
|
97
|
+
if (flags.json) {
|
|
98
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`\x1b[1mstyle-dataset-lab\x1b[0m select`);
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(` Selection: ${selectionId}`);
|
|
105
|
+
console.log(` Source: ${manifest.source_type} ${manifest.source_id}`);
|
|
106
|
+
if (manifest.workflow_id) console.log(` Workflow: ${manifest.workflow_id}`);
|
|
107
|
+
if (manifest.subject_id) console.log(` Subject: ${manifest.subject_id}`);
|
|
108
|
+
console.log(` Items: ${manifest.items.length}`);
|
|
109
|
+
console.log('');
|
|
110
|
+
|
|
111
|
+
for (const item of manifest.items) {
|
|
112
|
+
console.log(` ✓ ${item.slot_or_output.padEnd(28)} → ${item.filename} ${item.reason}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log('');
|
|
116
|
+
info(`Selection: selections/${selectionId}/manifest.json`);
|
|
117
|
+
info(`Chosen files: selections/${selectionId}/chosen/`);
|
|
118
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* selection-show.js — Show selection details or list all selections.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sdlab selection show --project star-freight
|
|
8
|
+
* sdlab selection show --selection selection_2026-04-16_001
|
|
9
|
+
* sdlab selection show selection_2026-04-16_001 --json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseArgs } from '../lib/args.js';
|
|
13
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
14
|
+
import { loadSelection, listSelections, getSelectionsDir } from '../lib/selections.js';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
20
|
+
flags: {
|
|
21
|
+
project: { type: 'string', default: 'star-freight' },
|
|
22
|
+
selection: { type: 'string' },
|
|
23
|
+
json: { type: 'boolean' },
|
|
24
|
+
},
|
|
25
|
+
deprecated: { game: 'project' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const projectName = flags.project;
|
|
29
|
+
const projectRoot = getProjectRoot(projectName);
|
|
30
|
+
const selectionId = positionals[0] || flags.selection;
|
|
31
|
+
|
|
32
|
+
// List mode
|
|
33
|
+
if (!selectionId) {
|
|
34
|
+
const selections = listSelections(projectRoot);
|
|
35
|
+
if (selections.length === 0) {
|
|
36
|
+
console.log('No selections found.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(`\x1b[1mstyle-dataset-lab\x1b[0m selections (${projectName})`);
|
|
40
|
+
console.log('');
|
|
41
|
+
for (const id of selections) {
|
|
42
|
+
try {
|
|
43
|
+
const m = loadSelection(projectRoot, id);
|
|
44
|
+
const items = m.items?.length || 0;
|
|
45
|
+
const src = `${m.source_type}:${m.source_id}`;
|
|
46
|
+
const ready = m.reingest_ready ? '✓' : '○';
|
|
47
|
+
const subject = m.subject_id ? ` (${m.subject_id})` : '';
|
|
48
|
+
console.log(` ${ready} ${id} ${src.padEnd(32)} ${items} items${subject}`);
|
|
49
|
+
} catch {
|
|
50
|
+
console.log(` ? ${id} (manifest unreadable)`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
console.log('');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Detail mode
|
|
58
|
+
const manifest = loadSelection(projectRoot, selectionId);
|
|
59
|
+
|
|
60
|
+
if (flags.json) {
|
|
61
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`\x1b[1mstyle-dataset-lab\x1b[0m selection ${selectionId}`);
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(` Source: ${manifest.source_type} ${manifest.source_id}`);
|
|
68
|
+
if (manifest.workflow_id) console.log(` Workflow: ${manifest.workflow_id}`);
|
|
69
|
+
if (manifest.subject_id) console.log(` Subject: ${manifest.subject_id}`);
|
|
70
|
+
console.log(` Created: ${manifest.created_at}`);
|
|
71
|
+
console.log(` Re-ingest ready: ${manifest.reingest_ready ? 'yes' : 'no'}`);
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(' Chosen outputs:');
|
|
74
|
+
for (const item of manifest.items || []) {
|
|
75
|
+
const seedStr = item.seed !== undefined ? `seed=${item.seed}` : '';
|
|
76
|
+
console.log(` ✓ ${item.slot_or_output.padEnd(28)} → ${item.filename} ${seedStr}`);
|
|
77
|
+
if (item.reason) console.log(` ${item.reason}`);
|
|
78
|
+
if (item.tags?.length) console.log(` tags: ${item.tags.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check provenance log
|
|
82
|
+
const provPath = join(getSelectionsDir(projectRoot), selectionId, 'provenance.jsonl');
|
|
83
|
+
if (existsSync(provPath)) {
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(' Provenance log: provenance.jsonl');
|
|
86
|
+
}
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
package/scripts/snapshot.js
CHANGED
|
@@ -32,39 +32,43 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
32
32
|
const profileIdx = args.indexOf('--profile');
|
|
33
33
|
const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
|
|
34
34
|
const profile = loadSelectionProfile(projectRoot, profileId);
|
|
35
|
+
const dryRun = argv.includes('--dry-run');
|
|
35
36
|
|
|
36
37
|
console.log(`\x1b[1msdlab snapshot create\x1b[0m — ${projectName}`);
|
|
37
38
|
console.log(` Profile: ${profileId || 'training-default (built-in)'}`);
|
|
39
|
+
if (dryRun) console.log(' Mode: DRY RUN (no files written)');
|
|
38
40
|
console.log('');
|
|
39
41
|
|
|
40
|
-
const result = await createSnapshot(projectRoot, profile);
|
|
42
|
+
const result = await createSnapshot(projectRoot, profile, { dryRun });
|
|
41
43
|
|
|
42
44
|
console.log(` \x1b[32m✓\x1b[0m Snapshot: ${result.snapshotId}`);
|
|
43
45
|
console.log(` Included: ${result.included}`);
|
|
44
46
|
console.log(` Excluded: ${result.excluded}`);
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
// Read and display summary
|
|
50
|
+
const summary = JSON.parse(
|
|
51
|
+
await readFile(join(projectRoot, 'snapshots', result.snapshotId, 'summary.json'), 'utf-8')
|
|
52
|
+
);
|
|
53
|
+
console.log('\n Lane distribution:');
|
|
54
|
+
for (const [lane, count] of Object.entries(summary.lane_distribution).sort((a, b) => b[1] - a[1])) {
|
|
55
|
+
console.log(` ${lane}: ${count}`);
|
|
56
|
+
}
|
|
57
|
+
console.log('\n Faction distribution:');
|
|
58
|
+
for (const [f, c] of Object.entries(summary.faction_distribution).sort((a, b) => b[1] - a[1])) {
|
|
59
|
+
console.log(` ${f}: ${c}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Read and categorize exclusions
|
|
63
|
+
const excludedRaw = await readFile(
|
|
64
|
+
join(projectRoot, 'snapshots', result.snapshotId, 'excluded.jsonl'), 'utf-8'
|
|
65
|
+
);
|
|
66
|
+
const excluded = excludedRaw.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
|
|
67
|
+
const categories = categorizeExclusions(excluded);
|
|
68
|
+
console.log('\n Exclusion reasons:');
|
|
69
|
+
for (const [cat, count] of Object.entries(categories)) {
|
|
70
|
+
if (count > 0) console.log(` ${cat}: ${count}`);
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
} else if (subcommand === 'list') {
|
package/scripts/split.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
42
42
|
const profileIdx = args.indexOf('--profile');
|
|
43
43
|
const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
|
|
44
44
|
const profile = loadSplitProfile(projectRoot, profileId);
|
|
45
|
+
const dryRun = argv.includes('--dry-run');
|
|
45
46
|
|
|
46
47
|
console.log(`\x1b[1msdlab split build\x1b[0m — ${projectName}`);
|
|
47
48
|
console.log(` Snapshot: ${snapshotId}`);
|
|
@@ -49,9 +50,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
49
50
|
console.log(` Strategy: ${profile.strategy}`);
|
|
50
51
|
console.log(` Ratios: ${profile.train_ratio}/${profile.val_ratio}/${profile.test_ratio}`);
|
|
51
52
|
console.log(` Seed: ${profile.seed}`);
|
|
53
|
+
if (dryRun) console.log(' Mode: DRY RUN (no files written)');
|
|
52
54
|
console.log('');
|
|
53
55
|
|
|
54
|
-
const result = await createSplit(projectRoot, snapshotId, profile);
|
|
56
|
+
const result = await createSplit(projectRoot, snapshotId, profile, { dryRun });
|
|
55
57
|
|
|
56
58
|
console.log(` \x1b[32m✓\x1b[0m Split: ${result.splitId}`);
|
|
57
59
|
console.log(` Train: ${result.train}`);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* workflow-list.js — List workflow profiles for a project.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sdlab workflow list --project star-freight
|
|
8
|
+
* sdlab workflow list --project star-freight --json
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { getProjectName, parseArgs } from '../lib/args.js';
|
|
12
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
13
|
+
import { listWorkflowProfiles } from '../lib/workflow-profiles.js';
|
|
14
|
+
|
|
15
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
16
|
+
const { flags } = parseArgs(argv, {
|
|
17
|
+
flags: {
|
|
18
|
+
project: { type: 'string', default: 'star-freight' },
|
|
19
|
+
json: { type: 'boolean' },
|
|
20
|
+
},
|
|
21
|
+
deprecated: { game: 'project' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const projectName = flags.project;
|
|
25
|
+
const projectRoot = getProjectRoot(projectName);
|
|
26
|
+
const profiles = await listWorkflowProfiles(projectRoot);
|
|
27
|
+
|
|
28
|
+
if (flags.json) {
|
|
29
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`\x1b[1msdlab workflow list\x1b[0m — ${projectName}\n`);
|
|
34
|
+
|
|
35
|
+
if (profiles.length === 0) {
|
|
36
|
+
console.log(' No workflow profiles found.');
|
|
37
|
+
console.log(` Add profiles to projects/${projectName}/workflows/profiles/`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Table header
|
|
42
|
+
const idW = Math.max(12, ...profiles.map(p => p.workflow_id.length));
|
|
43
|
+
const laneW = Math.max(6, ...profiles.map(p => p.lane_id.length));
|
|
44
|
+
const modeW = Math.max(7, ...profiles.map(p => (p.subject_mode || 'optional').length));
|
|
45
|
+
const outW = Math.max(11, ...profiles.map(p => p.output_mode.length));
|
|
46
|
+
|
|
47
|
+
const header = ` ${'WORKFLOW'.padEnd(idW)} ${'LANE'.padEnd(laneW)} ${'SUBJECT'.padEnd(modeW)} ${'OUTPUT MODE'.padEnd(outW)} COUNT`;
|
|
48
|
+
console.log(header);
|
|
49
|
+
console.log(' ' + '─'.repeat(header.length - 2));
|
|
50
|
+
|
|
51
|
+
for (const p of profiles) {
|
|
52
|
+
const line = ` ${p.workflow_id.padEnd(idW)} ${p.lane_id.padEnd(laneW)} ${(p.subject_mode || 'optional').padEnd(modeW)} ${p.output_mode.padEnd(outW)} ${p.output_count}`;
|
|
53
|
+
console.log(line);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`\n ${profiles.length} workflow(s)`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (process.argv[1] && (process.argv[1].endsWith('workflow-list.js') || process.argv[1].endsWith('workflow-list'))) {
|
|
60
|
+
run().catch((err) => { console.error(err.message || err); process.exit(1); });
|
|
61
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* workflow-show.js — Show details of a workflow profile.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sdlab workflow show character-portrait-set --project star-freight
|
|
8
|
+
* sdlab workflow show character-portrait-set --json
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parseArgs } from '../lib/args.js';
|
|
12
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
13
|
+
import { getWorkflowProfile } from '../lib/workflow-profiles.js';
|
|
14
|
+
|
|
15
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
16
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
17
|
+
flags: {
|
|
18
|
+
project: { type: 'string', default: 'star-freight' },
|
|
19
|
+
json: { type: 'boolean' },
|
|
20
|
+
},
|
|
21
|
+
positionals: ['workflowId'],
|
|
22
|
+
deprecated: { game: 'project' },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const workflowId = positionals[0];
|
|
26
|
+
if (!workflowId) {
|
|
27
|
+
console.log('Usage: sdlab workflow show <workflow-id> [--project <name>]');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const projectRoot = getProjectRoot(flags.project);
|
|
32
|
+
const profile = await getWorkflowProfile(projectRoot, workflowId);
|
|
33
|
+
|
|
34
|
+
if (flags.json) {
|
|
35
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`\x1b[1msdlab workflow show\x1b[0m — ${workflowId}\n`);
|
|
40
|
+
console.log(` Label: ${profile.label}`);
|
|
41
|
+
if (profile.description) {
|
|
42
|
+
console.log(` Description: ${profile.description}`);
|
|
43
|
+
}
|
|
44
|
+
console.log(` Lane: ${profile.lane_id}`);
|
|
45
|
+
console.log(` Subject: ${profile.subject_mode || 'optional'}`);
|
|
46
|
+
console.log(` Output mode: ${profile.output_mode}`);
|
|
47
|
+
console.log(` Output count: ${profile.output_count}`);
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
console.log(' \x1b[1mPrompt strategy:\x1b[0m');
|
|
51
|
+
console.log(` Style prefix: ${profile.prompt_strategy.style_prefix.join(', ')}`);
|
|
52
|
+
console.log(` Structure: ${profile.prompt_strategy.structure}`);
|
|
53
|
+
console.log(` Must include: ${profile.prompt_strategy.must_include.join(', ')}`);
|
|
54
|
+
if (profile.prompt_strategy.optional_blocks?.length > 0) {
|
|
55
|
+
console.log(` Optional: ${profile.prompt_strategy.optional_blocks.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
console.log('');
|
|
58
|
+
|
|
59
|
+
console.log(' \x1b[1mNegative strategy:\x1b[0m');
|
|
60
|
+
console.log(` Must avoid: ${profile.negative_strategy.must_avoid.join(', ')}`);
|
|
61
|
+
if (profile.negative_strategy.lane_specific_avoid?.length > 0) {
|
|
62
|
+
console.log(` Lane avoid: ${profile.negative_strategy.lane_specific_avoid.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
|
|
66
|
+
console.log(' \x1b[1mCanon focus:\x1b[0m');
|
|
67
|
+
console.log(` ${profile.canon_focus.join(', ')}`);
|
|
68
|
+
console.log('');
|
|
69
|
+
|
|
70
|
+
console.log(' \x1b[1mDrift guards:\x1b[0m');
|
|
71
|
+
for (const guard of profile.drift_guards) {
|
|
72
|
+
console.log(` ⚠ ${guard}`);
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
|
|
76
|
+
console.log(' \x1b[1mRuntime defaults:\x1b[0m');
|
|
77
|
+
const rd = profile.runtime_defaults;
|
|
78
|
+
console.log(` Adapter: ${rd.adapter_target}`);
|
|
79
|
+
if (rd.width) console.log(` Size: ${rd.width}×${rd.height || rd.width}`);
|
|
80
|
+
if (rd.steps) console.log(` Steps: ${rd.steps}`);
|
|
81
|
+
if (rd.cfg) console.log(` CFG: ${rd.cfg}`);
|
|
82
|
+
if (rd.sampler) console.log(` Sampler: ${rd.sampler}`);
|
|
83
|
+
if (rd.seed_mode) console.log(` Seed mode: ${rd.seed_mode}`);
|
|
84
|
+
|
|
85
|
+
if (profile.required_reference_roles?.length > 0) {
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(` \x1b[1mRequired references:\x1b[0m ${profile.required_reference_roles.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
if (profile.preferred_reference_tags?.length > 0) {
|
|
90
|
+
console.log(` \x1b[1mPreferred tags:\x1b[0m ${profile.preferred_reference_tags.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (process.argv[1] && (process.argv[1].endsWith('workflow-show.js') || process.argv[1].endsWith('workflow-show'))) {
|
|
95
|
+
run().catch((err) => { console.error(err.message || err); process.exit(1); });
|
|
96
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workflow_id": "environment-moodboard",
|
|
3
|
+
"label": "Environment Moodboard",
|
|
4
|
+
"description": "Explore architectural mood, palette, and spatial atmosphere.",
|
|
5
|
+
"lane_id": "exterior",
|
|
6
|
+
"subject_mode": "optional",
|
|
7
|
+
"output_mode": "moodboard",
|
|
8
|
+
"output_count": 6,
|
|
9
|
+
"required_reference_roles": [],
|
|
10
|
+
"preferred_reference_tags": ["exterior", "approved"],
|
|
11
|
+
"prompt_strategy": {
|
|
12
|
+
"style_prefix": [
|
|
13
|
+
"architectural concept art moodboard",
|
|
14
|
+
"strong spatial atmosphere",
|
|
15
|
+
"painterly rendering"
|
|
16
|
+
],
|
|
17
|
+
"structure": "style_prefix, lane_goal, canon_focus, controlled_variation",
|
|
18
|
+
"must_include": [
|
|
19
|
+
"spatial composition",
|
|
20
|
+
"material palette",
|
|
21
|
+
"lighting atmosphere"
|
|
22
|
+
],
|
|
23
|
+
"optional_blocks": [
|
|
24
|
+
"time of day note",
|
|
25
|
+
"weather note",
|
|
26
|
+
"era note"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"negative_strategy": {
|
|
30
|
+
"must_avoid": [
|
|
31
|
+
"anachronistic elements",
|
|
32
|
+
"wrong architectural era",
|
|
33
|
+
"clean sterile surfaces",
|
|
34
|
+
"modern materials in historical context"
|
|
35
|
+
],
|
|
36
|
+
"lane_specific_avoid": [
|
|
37
|
+
"character focus"
|
|
38
|
+
],
|
|
39
|
+
"subject_specific_avoid": []
|
|
40
|
+
},
|
|
41
|
+
"canon_focus": [
|
|
42
|
+
"palette",
|
|
43
|
+
"material_language",
|
|
44
|
+
"lighting",
|
|
45
|
+
"scale_logic",
|
|
46
|
+
"era_logic"
|
|
47
|
+
],
|
|
48
|
+
"drift_guards": [
|
|
49
|
+
"architectural era must remain consistent",
|
|
50
|
+
"material palette must match project canon",
|
|
51
|
+
"scale must read correctly"
|
|
52
|
+
],
|
|
53
|
+
"runtime_defaults": {
|
|
54
|
+
"adapter_target": "comfyui",
|
|
55
|
+
"width": 1024,
|
|
56
|
+
"height": 1024,
|
|
57
|
+
"steps": 30,
|
|
58
|
+
"cfg": 6.5,
|
|
59
|
+
"sampler": "dpmpp_2m",
|
|
60
|
+
"seed_mode": "random"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workflow_id": "interior-variant-pack",
|
|
3
|
+
"label": "Interior Variant Pack",
|
|
4
|
+
"description": "Generate interior space variations exploring layout, mood, and materials.",
|
|
5
|
+
"lane_id": "interior",
|
|
6
|
+
"subject_mode": "optional",
|
|
7
|
+
"output_mode": "variant_pack",
|
|
8
|
+
"output_count": 4,
|
|
9
|
+
"required_reference_roles": [],
|
|
10
|
+
"preferred_reference_tags": ["interior", "approved"],
|
|
11
|
+
"prompt_strategy": {
|
|
12
|
+
"style_prefix": [
|
|
13
|
+
"interior space concept art",
|
|
14
|
+
"atmospheric spatial rendering",
|
|
15
|
+
"functional layout emphasis"
|
|
16
|
+
],
|
|
17
|
+
"structure": "style_prefix, lane_goal, canon_focus, controlled_variation",
|
|
18
|
+
"must_include": [
|
|
19
|
+
"spatial function",
|
|
20
|
+
"material palette",
|
|
21
|
+
"lighting mood"
|
|
22
|
+
],
|
|
23
|
+
"optional_blocks": [
|
|
24
|
+
"furniture note",
|
|
25
|
+
"activity note"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"negative_strategy": {
|
|
29
|
+
"must_avoid": [
|
|
30
|
+
"empty featureless rooms",
|
|
31
|
+
"modern furniture in period setting",
|
|
32
|
+
"over-cluttered composition"
|
|
33
|
+
],
|
|
34
|
+
"lane_specific_avoid": [
|
|
35
|
+
"exterior views dominating"
|
|
36
|
+
],
|
|
37
|
+
"subject_specific_avoid": []
|
|
38
|
+
},
|
|
39
|
+
"canon_focus": [
|
|
40
|
+
"material_language",
|
|
41
|
+
"lighting",
|
|
42
|
+
"composition",
|
|
43
|
+
"era_logic",
|
|
44
|
+
"surface_wear"
|
|
45
|
+
],
|
|
46
|
+
"drift_guards": [
|
|
47
|
+
"materials must match architectural era",
|
|
48
|
+
"lighting must serve spatial mood",
|
|
49
|
+
"wear must read authentically"
|
|
50
|
+
],
|
|
51
|
+
"runtime_defaults": {
|
|
52
|
+
"adapter_target": "comfyui",
|
|
53
|
+
"width": 1024,
|
|
54
|
+
"height": 1024,
|
|
55
|
+
"steps": 30,
|
|
56
|
+
"cfg": 6.5,
|
|
57
|
+
"sampler": "dpmpp_2m",
|
|
58
|
+
"seed_mode": "random"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workflow_id": "character-portrait-set",
|
|
3
|
+
"label": "Character Portrait Set",
|
|
4
|
+
"description": "Generate portrait candidates for a named character.",
|
|
5
|
+
"lane_id": "portrait",
|
|
6
|
+
"subject_mode": "required",
|
|
7
|
+
"output_mode": "portrait_set",
|
|
8
|
+
"output_count": 4,
|
|
9
|
+
"required_reference_roles": ["anchor"],
|
|
10
|
+
"preferred_reference_tags": ["portrait", "approved"],
|
|
11
|
+
"prompt_strategy": {
|
|
12
|
+
"style_prefix": [
|
|
13
|
+
"character portrait concept art",
|
|
14
|
+
"high fidelity facial detail",
|
|
15
|
+
"clean readable silhouette"
|
|
16
|
+
],
|
|
17
|
+
"structure": "style_prefix, subject_identity, lane_goal, canon_focus, controlled_variation",
|
|
18
|
+
"must_include": [
|
|
19
|
+
"subject identity",
|
|
20
|
+
"costume logic",
|
|
21
|
+
"facial detail"
|
|
22
|
+
],
|
|
23
|
+
"optional_blocks": [
|
|
24
|
+
"lighting note",
|
|
25
|
+
"expression note"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"negative_strategy": {
|
|
29
|
+
"must_avoid": [
|
|
30
|
+
"off-model face",
|
|
31
|
+
"extra accessories",
|
|
32
|
+
"ornamental drift"
|
|
33
|
+
],
|
|
34
|
+
"lane_specific_avoid": [
|
|
35
|
+
"full body composition",
|
|
36
|
+
"background complexity"
|
|
37
|
+
],
|
|
38
|
+
"subject_specific_avoid": []
|
|
39
|
+
},
|
|
40
|
+
"canon_focus": [
|
|
41
|
+
"silhouette",
|
|
42
|
+
"costume_logic",
|
|
43
|
+
"palette",
|
|
44
|
+
"anatomy",
|
|
45
|
+
"gesture"
|
|
46
|
+
],
|
|
47
|
+
"drift_guards": [
|
|
48
|
+
"preserve face structure",
|
|
49
|
+
"preserve age read",
|
|
50
|
+
"preserve canonical materials"
|
|
51
|
+
],
|
|
52
|
+
"runtime_defaults": {
|
|
53
|
+
"adapter_target": "comfyui",
|
|
54
|
+
"width": 1024,
|
|
55
|
+
"height": 1024,
|
|
56
|
+
"steps": 30,
|
|
57
|
+
"cfg": 6.5,
|
|
58
|
+
"sampler": "dpmpp_2m",
|
|
59
|
+
"seed_mode": "increment"
|
|
60
|
+
}
|
|
61
|
+
}
|