@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/CODE_OF_CONDUCT.md +9 -0
  3. package/CONTRIBUTING.md +71 -0
  4. package/README.md +82 -9
  5. package/SECURITY.md +6 -5
  6. package/bin/sdlab.js +359 -11
  7. package/lib/adapters/comfyui-runner.js +170 -0
  8. package/lib/adapters/comfyui-workflows.js +163 -0
  9. package/lib/adapters/diffusers-lora.js +7 -2
  10. package/lib/adapters/generic-image-caption.js +6 -1
  11. package/lib/args.js +200 -13
  12. package/lib/batch-compiler.js +122 -0
  13. package/lib/batch-modes.js +111 -0
  14. package/lib/batch-runs.js +237 -0
  15. package/lib/batch-sheet-render.js +209 -0
  16. package/lib/brief-compiler.js +625 -0
  17. package/lib/brief-render.js +205 -0
  18. package/lib/card.js +11 -4
  19. package/lib/comfyui.js +100 -6
  20. package/lib/config.js +68 -3
  21. package/lib/critique-engine.js +413 -0
  22. package/lib/critique-render.js +171 -0
  23. package/lib/deprecation.js +1 -1
  24. package/lib/eval-pack.js +94 -4
  25. package/lib/eval-runs.js +42 -3
  26. package/lib/export.js +144 -34
  27. package/lib/generated-provenance.js +87 -0
  28. package/lib/implementation-packs.js +15 -1
  29. package/lib/log.js +41 -4
  30. package/lib/paths.js +72 -3
  31. package/lib/refine-briefs.js +336 -0
  32. package/lib/reingest-selected.js +184 -0
  33. package/lib/reingest.js +15 -1
  34. package/lib/run-summary.js +132 -0
  35. package/lib/runtime-runs.js +218 -0
  36. package/lib/selections.js +375 -0
  37. package/lib/snapshot.js +100 -7
  38. package/lib/split.js +175 -17
  39. package/lib/training-manifests.js +17 -3
  40. package/lib/training-packages.js +28 -14
  41. package/lib/workflow-profiles.js +161 -0
  42. package/package.json +13 -4
  43. package/scripts/batch-generate.js +176 -0
  44. package/scripts/batch-sheet.js +46 -0
  45. package/scripts/batch-show.js +86 -0
  46. package/scripts/brief-compile.js +134 -0
  47. package/scripts/brief-show.js +53 -0
  48. package/scripts/canon-bind.js +7 -9
  49. package/scripts/card.js +22 -25
  50. package/scripts/compare.js +38 -33
  51. package/scripts/critique-show.js +50 -0
  52. package/scripts/critique.js +70 -0
  53. package/scripts/curate.js +91 -46
  54. package/scripts/doctor.js +172 -6
  55. package/scripts/eligibility.js +17 -14
  56. package/scripts/eval-pack.js +13 -11
  57. package/scripts/eval-run.js +30 -29
  58. package/scripts/export.js +27 -31
  59. package/scripts/generate-controlnet.js +31 -17
  60. package/scripts/generate-identity.js +37 -20
  61. package/scripts/generate-ipadapter.js +22 -16
  62. package/scripts/generate.js +68 -13
  63. package/scripts/implementation-pack.js +19 -14
  64. package/scripts/init.js +137 -17
  65. package/scripts/painterly.js +8 -10
  66. package/scripts/refine.js +119 -0
  67. package/scripts/reingest-selected.js +78 -0
  68. package/scripts/reingest.js +37 -18
  69. package/scripts/run-generate.js +76 -0
  70. package/scripts/run-list.js +58 -0
  71. package/scripts/run-show.js +63 -0
  72. package/scripts/select.js +119 -0
  73. package/scripts/selection-show.js +88 -0
  74. package/scripts/snapshot.js +22 -24
  75. package/scripts/split.js +26 -31
  76. package/scripts/training-manifest.js +29 -24
  77. package/scripts/training-package.js +24 -20
  78. package/scripts/training-profile.js +13 -8
  79. package/scripts/workflow-list.js +61 -0
  80. package/scripts/workflow-show.js +97 -0
  81. package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
  82. package/templates/domains/architecture/terminology.json +10 -8
  83. package/templates/domains/architecture/workflows/profiles/environment-moodboard.json +62 -0
  84. package/templates/domains/architecture/workflows/profiles/interior-variant-pack.json +60 -0
  85. package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
  86. package/templates/domains/character-design/project.json +4 -2
  87. package/templates/domains/character-design/workflows/profiles/character-portrait-set.json +61 -0
  88. package/templates/domains/character-design/workflows/profiles/expression-sheet.json +59 -0
  89. package/templates/domains/creature-design/inputs/prompts/example-wave.json +32 -0
  90. package/templates/domains/creature-design/project.json +4 -2
  91. package/templates/domains/creature-design/terminology.json +1 -0
  92. package/templates/domains/creature-design/workflows/profiles/creature-portrait-set.json +61 -0
  93. package/templates/domains/creature-design/workflows/profiles/silhouette-exploration.json +58 -0
  94. package/templates/domains/game-art/inputs/prompts/example-wave.json +37 -0
  95. package/templates/domains/game-art/project.json +4 -2
  96. package/templates/domains/game-art/workflows/profiles/character-portrait-set.json +62 -0
  97. package/templates/domains/game-art/workflows/profiles/environment-moodboard.json +61 -0
  98. package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
  99. package/templates/domains/vehicle-mech/project.json +4 -2
  100. package/templates/domains/vehicle-mech/workflows/profiles/variant-pack.json +60 -0
  101. package/templates/domains/vehicle-mech/workflows/profiles/vehicle-silhouette-pack.json +59 -0
  102. package/scripts/bulk-curate-wave2-5.js +0 -138
  103. package/scripts/bulk-curate-waves11-18.js +0 -413
  104. package/scripts/curate-wave25.js +0 -209
  105. package/scripts/migrate-records.js +0 -141
  106. package/templates/inputs/prompts/example-wave.json +0 -27
package/scripts/split.js CHANGED
@@ -10,43 +10,45 @@
10
10
  * sdlab split audit <split-id> [--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';
14
+ import { parseArgs, getProjectName } from '../lib/args.js';
16
15
  import { REPO_ROOT } from '../lib/paths.js';
17
16
  import { loadSplitProfile } from '../lib/config.js';
17
+ import { inputError, handleCliError } from '../lib/errors.js';
18
18
  import { listSnapshots } from '../lib/snapshot.js';
19
19
  import { createSplit, loadSplit, loadSplitAudit, listSplits } from '../lib/split.js';
20
20
 
21
21
  export async function run(argv = process.argv.slice(2)) {
22
- const projectName = getProjectName(argv);
22
+ const { flags, positionals } = parseArgs(argv, {
23
+ flags: {
24
+ project: { type: 'string' },
25
+ snapshot: { type: 'string' },
26
+ profile: { type: 'string' },
27
+ 'dry-run': { type: 'boolean', default: false },
28
+ },
29
+ deprecated: { game: 'project' },
30
+ });
31
+
32
+ const projectName = flags.project || getProjectName(argv);
23
33
  const projectRoot = join(REPO_ROOT, 'projects', projectName);
24
- const subcommand = argv.find(a => !a.startsWith('--')) || 'list';
25
- const args = argv.filter(a => a !== subcommand);
34
+ const subcommand = positionals[0] || 'list';
35
+ const dryRun = flags['dry-run'] || flags.dryRun;
26
36
 
27
37
  if (subcommand === 'build') {
28
- // Find snapshot ID
29
- let snapshotId;
30
- const snapIdx = args.indexOf('--snapshot');
31
- if (snapIdx >= 0) {
32
- snapshotId = args[snapIdx + 1];
33
- } else {
34
- // Use latest snapshot
38
+ let snapshotId = flags.snapshot;
39
+ if (!snapshotId) {
35
40
  const snapshots = await listSnapshots(projectRoot);
36
41
  if (snapshots.length === 0) {
37
- throw new Error('No snapshots found. Run: sdlab snapshot create');
42
+ throw inputError('INPUT_NO_SNAPSHOT', 'No snapshots found. Run: sdlab snapshot create');
38
43
  }
39
44
  snapshotId = snapshots[snapshots.length - 1].id;
40
45
  }
41
46
 
42
- const profileIdx = args.indexOf('--profile');
43
- const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
44
- const profile = loadSplitProfile(projectRoot, profileId);
45
- const dryRun = argv.includes('--dry-run');
47
+ const profile = loadSplitProfile(projectRoot, flags.profile);
46
48
 
47
49
  console.log(`\x1b[1msdlab split build\x1b[0m — ${projectName}`);
48
50
  console.log(` Snapshot: ${snapshotId}`);
49
- console.log(` Profile: ${profileId || 'balanced-default (built-in)'}`);
51
+ console.log(` Profile: ${flags.profile || 'balanced-default (built-in)'}`);
50
52
  console.log(` Strategy: ${profile.strategy}`);
51
53
  console.log(` Ratios: ${profile.train_ratio}/${profile.val_ratio}/${profile.test_ratio}`);
52
54
  console.log(` Seed: ${profile.seed}`);
@@ -60,7 +62,6 @@ export async function run(argv = process.argv.slice(2)) {
60
62
  console.log(` Val: ${result.val}`);
61
63
  console.log(` Test: ${result.test}`);
62
64
 
63
- // Show audit summary
64
65
  const audit = await loadSplitAudit(projectRoot, result.splitId);
65
66
  console.log('');
66
67
  if (audit.leakage_check.passed) {
@@ -86,8 +87,8 @@ export async function run(argv = process.argv.slice(2)) {
86
87
  }
87
88
 
88
89
  } else if (subcommand === 'show') {
89
- const splitId = args.find(a => a.startsWith('split-'));
90
- if (!splitId) throw new Error('Usage: sdlab split show <split-id>');
90
+ const splitId = positionals[1] || positionals.find(a => a.startsWith('split-'));
91
+ if (!splitId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab split show <split-id>');
91
92
 
92
93
  const manifest = await loadSplit(projectRoot, splitId);
93
94
  console.log(`\x1b[1mSplit\x1b[0m — ${splitId}\n`);
@@ -112,13 +113,12 @@ export async function run(argv = process.argv.slice(2)) {
112
113
  }
113
114
 
114
115
  } else if (subcommand === 'audit') {
115
- const splitId = args.find(a => a.startsWith('split-'));
116
- if (!splitId) throw new Error('Usage: sdlab split audit <split-id>');
116
+ const splitId = positionals[1] || positionals.find(a => a.startsWith('split-'));
117
+ if (!splitId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab split audit <split-id>');
117
118
 
118
119
  const audit = await loadSplitAudit(projectRoot, splitId);
119
120
  console.log(`\x1b[1mSplit audit\x1b[0m — ${splitId}\n`);
120
121
 
121
- // Leakage check
122
122
  if (audit.leakage_check.passed) {
123
123
  console.log(` \x1b[32m✓\x1b[0m Leakage check: PASSED`);
124
124
  } else {
@@ -128,13 +128,11 @@ export async function run(argv = process.argv.slice(2)) {
128
128
  }
129
129
  }
130
130
 
131
- // Family distribution
132
131
  console.log(`\n Families: ${audit.family_count}`);
133
132
  console.log(` Train: ${audit.families_per_split.train}`);
134
133
  console.log(` Val: ${audit.families_per_split.val}`);
135
134
  console.log(` Test: ${audit.families_per_split.test}`);
136
135
 
137
- // Lane balance
138
136
  console.log('\n Lane balance:');
139
137
  console.log(` ${'Lane'.padEnd(20)} ${'Total'.padStart(6)} ${'Train%'.padStart(8)} ${'Val%'.padStart(8)} ${'Test%'.padStart(8)}`);
140
138
  console.log(` ${'─'.repeat(52)}`);
@@ -143,14 +141,11 @@ export async function run(argv = process.argv.slice(2)) {
143
141
  }
144
142
 
145
143
  } else {
146
- throw new Error(`Unknown subcommand: ${subcommand}. Use: build, list, show, audit`);
144
+ throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, list, show, audit`);
147
145
  }
148
146
  }
149
147
 
150
148
  // Direct execution guard
151
149
  if (process.argv[1] && (process.argv[1].endsWith('split.js') || process.argv[1].endsWith('split'))) {
152
- run().catch((err) => {
153
- console.error(err.message || err);
154
- process.exit(1);
155
- });
150
+ run().catch(handleCliError);
156
151
  }
@@ -11,8 +11,9 @@
11
11
  */
12
12
 
13
13
  import { join } from 'node:path';
14
- import { getProjectName } from '../lib/args.js';
14
+ import { parseArgs, getProjectName } from '../lib/args.js';
15
15
  import { REPO_ROOT } from '../lib/paths.js';
16
+ import { inputError, handleCliError } from '../lib/errors.js';
16
17
  import { listExports } from '../lib/export.js';
17
18
  import {
18
19
  createTrainingManifest,
@@ -22,30 +23,34 @@ import {
22
23
  } from '../lib/training-manifests.js';
23
24
 
24
25
  export async function run(argv = process.argv.slice(2)) {
25
- const projectName = getProjectName(argv);
26
+ const { flags, positionals } = parseArgs(argv, {
27
+ flags: {
28
+ project: { type: 'string' },
29
+ export: { type: 'string' },
30
+ profile: { type: 'string' },
31
+ adapter: { type: 'string' },
32
+ 'base-model': { type: 'string' },
33
+ },
34
+ deprecated: { game: 'project' },
35
+ });
36
+
37
+ const projectName = flags.project || getProjectName(argv);
26
38
  const projectRoot = join(REPO_ROOT, 'projects', projectName);
27
- const subcommand = argv.find(a => !a.startsWith('--') && !a.startsWith('tm-')) || 'list';
28
- const args = argv.filter(a => a !== subcommand);
39
+ const subcommand = positionals.find(a => !a.startsWith('tm-')) || 'list';
29
40
 
30
41
  if (subcommand === 'create') {
31
- // Required: --export and --profile
32
- const exportIdx = args.indexOf('--export');
33
- if (exportIdx < 0) {
34
- // Try latest export
42
+ let exportId = flags.export;
43
+ if (!exportId) {
35
44
  const exports = await listExports(projectRoot);
36
- if (exports.length === 0) throw new Error('No exports found. Run: sdlab export build');
45
+ if (exports.length === 0) throw inputError('INPUT_NO_EXPORT', 'No exports found. Run: sdlab export build');
46
+ exportId = exports[exports.length - 1].id;
37
47
  }
38
- const exportId = exportIdx >= 0 ? args[exportIdx + 1] : (await listExports(projectRoot)).pop().id;
39
48
 
40
- const profileIdx = args.indexOf('--profile');
41
- if (profileIdx < 0) throw new Error('--profile is required. Use: sdlab training-profile list');
42
- const profileId = args[profileIdx + 1];
49
+ if (!flags.profile) throw inputError('INPUT_MISSING_FLAG', '--profile is required. Use: sdlab training-profile list');
50
+ const profileId = flags.profile;
43
51
 
44
- const adapterIdx = args.indexOf('--adapter');
45
- const adapterTarget = adapterIdx >= 0 ? args[adapterIdx + 1] : undefined;
46
-
47
- const baseModelIdx = args.indexOf('--base-model');
48
- const baseModel = baseModelIdx >= 0 ? args[baseModelIdx + 1] : undefined;
52
+ const adapterTarget = flags.adapter;
53
+ const baseModel = flags['base-model'] || flags.baseModel;
49
54
 
50
55
  console.log(`\x1b[1msdlab training-manifest create\x1b[0m — ${projectName}`);
51
56
  console.log(` Export: ${exportId}`);
@@ -62,8 +67,8 @@ export async function run(argv = process.argv.slice(2)) {
62
67
  console.log(` \x1b[32m✓\x1b[0m Manifest: ${result.manifestId}`);
63
68
 
64
69
  } else if (subcommand === 'validate') {
65
- const manifestId = args.find(a => a.startsWith('tm-'));
66
- if (!manifestId) throw new Error('Usage: sdlab training-manifest validate <manifest-id>');
70
+ const manifestId = positionals.find(a => a.startsWith('tm-'));
71
+ if (!manifestId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab training-manifest validate <manifest-id>');
67
72
 
68
73
  console.log(`\x1b[1msdlab training-manifest validate\x1b[0m — ${manifestId}\n`);
69
74
  const { valid, issues } = await validateTrainingManifest(projectRoot, manifestId);
@@ -78,8 +83,8 @@ export async function run(argv = process.argv.slice(2)) {
78
83
  }
79
84
 
80
85
  } else if (subcommand === 'show') {
81
- const manifestId = args.find(a => a.startsWith('tm-'));
82
- if (!manifestId) throw new Error('Usage: sdlab training-manifest show <manifest-id>');
86
+ const manifestId = positionals.find(a => a.startsWith('tm-'));
87
+ if (!manifestId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab training-manifest show <manifest-id>');
83
88
 
84
89
  const manifest = await loadTrainingManifest(projectRoot, manifestId);
85
90
  console.log(`\x1b[1mTraining manifest\x1b[0m — ${manifestId}\n`);
@@ -110,10 +115,10 @@ export async function run(argv = process.argv.slice(2)) {
110
115
  }
111
116
 
112
117
  } else {
113
- throw new Error(`Unknown subcommand: ${subcommand}. Use: create, validate, show, list`);
118
+ throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: create, validate, show, list`);
114
119
  }
115
120
  }
116
121
 
117
122
  if (process.argv[1] && (process.argv[1].endsWith('training-manifest.js') || process.argv[1].endsWith('training-manifest'))) {
118
- run().catch((err) => { console.error(err.message || err); process.exit(1); });
123
+ run().catch(handleCliError);
119
124
  }
@@ -10,48 +10,52 @@
10
10
  */
11
11
 
12
12
  import { join } from 'node:path';
13
- import { getProjectName } from '../lib/args.js';
13
+ import { parseArgs, getProjectName } from '../lib/args.js';
14
14
  import { REPO_ROOT } from '../lib/paths.js';
15
+ import { inputError, handleCliError } from '../lib/errors.js';
15
16
  import { listTrainingManifests } from '../lib/training-manifests.js';
16
17
  import { buildTrainingPackage, listTrainingPackages, loadTrainingPackage } from '../lib/training-packages.js';
17
18
 
18
19
  export async function run(argv = process.argv.slice(2)) {
19
- const projectName = getProjectName(argv);
20
+ const { flags, positionals } = parseArgs(argv, {
21
+ flags: {
22
+ project: { type: 'string' },
23
+ manifest: { type: 'string' },
24
+ adapter: { type: 'string' },
25
+ copy: { type: 'boolean', default: false },
26
+ },
27
+ deprecated: { game: 'project' },
28
+ });
29
+
30
+ const projectName = flags.project || getProjectName(argv);
20
31
  const projectRoot = join(REPO_ROOT, 'projects', projectName);
21
- const subcommand = argv.find(a => !a.startsWith('--') && !a.startsWith('tp-') && !a.startsWith('tm-')) || 'list';
22
- const args = argv.filter(a => a !== subcommand);
32
+ const subcommand = positionals.find(a => !a.startsWith('tp-') && !a.startsWith('tm-')) || 'list';
23
33
 
24
34
  if (subcommand === 'build') {
25
- // Find manifest
26
- let manifestId;
27
- const manifestIdx = args.indexOf('--manifest');
28
- if (manifestIdx >= 0) {
29
- manifestId = args[manifestIdx + 1];
30
- } else {
35
+ let manifestId = flags.manifest;
36
+ if (!manifestId) {
31
37
  const manifests = await listTrainingManifests(projectRoot);
32
- if (manifests.length === 0) throw new Error('No training manifests found. Run: sdlab training-manifest create');
38
+ if (manifests.length === 0) throw inputError('INPUT_NO_MANIFEST', 'No training manifests found. Run: sdlab training-manifest create');
33
39
  manifestId = manifests[manifests.length - 1].id;
34
40
  }
35
41
 
36
- const adapterIdx = args.indexOf('--adapter');
37
- const adapterOverride = adapterIdx >= 0 ? args[adapterIdx + 1] : undefined;
38
- const copy = args.includes('--copy');
42
+ const adapterOverride = flags.adapter;
39
43
 
40
44
  console.log(`\x1b[1msdlab training-package build\x1b[0m — ${projectName}`);
41
45
  console.log(` Manifest: ${manifestId}`);
42
46
  if (adapterOverride) console.log(` Adapter: ${adapterOverride}`);
43
- console.log(` Images: ${copy ? 'copy' : 'symlink'}`);
47
+ console.log(` Images: ${flags.copy ? 'copy' : 'symlink'}`);
44
48
  console.log('');
45
49
 
46
- const result = await buildTrainingPackage(projectRoot, manifestId, { copy, adapterOverride });
50
+ const result = await buildTrainingPackage(projectRoot, manifestId, { copy: flags.copy, adapterOverride });
47
51
 
48
52
  console.log(` \x1b[32m✓\x1b[0m Package: ${result.packageId}`);
49
53
  console.log(` Records: ${result.records}`);
50
54
  console.log(` Images: ${result.images}`);
51
55
 
52
56
  } else if (subcommand === 'show') {
53
- const packageId = args.find(a => a.startsWith('tp-'));
54
- if (!packageId) throw new Error('Usage: sdlab training-package show <package-id>');
57
+ const packageId = positionals.find(a => a.startsWith('tp-'));
58
+ if (!packageId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab training-package show <package-id>');
55
59
 
56
60
  const pkg = await loadTrainingPackage(projectRoot, packageId);
57
61
  console.log(`\x1b[1mTraining package\x1b[0m — ${packageId}\n`);
@@ -75,10 +79,10 @@ export async function run(argv = process.argv.slice(2)) {
75
79
  }
76
80
 
77
81
  } else {
78
- throw new Error(`Unknown subcommand: ${subcommand}. Use: build, show, list`);
82
+ throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, show, list`);
79
83
  }
80
84
  }
81
85
 
82
86
  if (process.argv[1] && (process.argv[1].endsWith('training-package.js') || process.argv[1].endsWith('training-package'))) {
83
- run().catch((err) => { console.error(err.message || err); process.exit(1); });
87
+ run().catch(handleCliError);
84
88
  }
@@ -9,15 +9,20 @@
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 { listTrainingProfiles, loadTrainingProfile } from '../lib/training-profiles.js';
15
16
 
16
17
  export async function run(argv = process.argv.slice(2)) {
17
- const projectName = getProjectName(argv);
18
+ const { flags, positionals } = parseArgs(argv, {
19
+ flags: { project: { type: 'string' } },
20
+ deprecated: { game: 'project' },
21
+ });
22
+
23
+ const projectName = flags.project || getProjectName(argv);
18
24
  const projectRoot = join(REPO_ROOT, 'projects', projectName);
19
- const subcommand = argv.find(a => !a.startsWith('--')) || 'list';
20
- const args = argv.filter(a => a !== subcommand);
25
+ const subcommand = positionals[0] || 'list';
21
26
 
22
27
  if (subcommand === 'list') {
23
28
  const profiles = await listTrainingProfiles(projectRoot);
@@ -31,8 +36,8 @@ export async function run(argv = process.argv.slice(2)) {
31
36
  }
32
37
 
33
38
  } else if (subcommand === 'show') {
34
- const profileId = args.find(a => !a.startsWith('--'));
35
- if (!profileId) throw new Error('Usage: sdlab training-profile show <profile-id>');
39
+ const profileId = positionals[1];
40
+ if (!profileId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab training-profile show <profile-id>');
36
41
 
37
42
  const profile = await loadTrainingProfile(projectRoot, profileId);
38
43
  console.log(`\x1b[1mTraining profile\x1b[0m — ${profileId}\n`);
@@ -54,10 +59,10 @@ export async function run(argv = process.argv.slice(2)) {
54
59
  }
55
60
 
56
61
  } else {
57
- throw new Error(`Unknown subcommand: ${subcommand}. Use: list, show`);
62
+ throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: list, show`);
58
63
  }
59
64
  }
60
65
 
61
66
  if (process.argv[1] && (process.argv[1].endsWith('training-profile.js') || process.argv[1].endsWith('training-profile'))) {
62
- run().catch((err) => { console.error(err.message || err); process.exit(1); });
67
+ run().catch(handleCliError);
63
68
  }
@@ -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' },
19
+ json: { type: 'boolean' },
20
+ },
21
+ deprecated: { game: 'project' },
22
+ });
23
+
24
+ const projectName = flags.project || getProjectName(argv);
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,97 @@
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, getProjectName } 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' },
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 projectName = flags.project || getProjectName(argv);
32
+ const projectRoot = getProjectRoot(projectName);
33
+ const profile = await getWorkflowProfile(projectRoot, workflowId);
34
+
35
+ if (flags.json) {
36
+ console.log(JSON.stringify(profile, null, 2));
37
+ return;
38
+ }
39
+
40
+ console.log(`\x1b[1msdlab workflow show\x1b[0m — ${workflowId}\n`);
41
+ console.log(` Label: ${profile.label}`);
42
+ if (profile.description) {
43
+ console.log(` Description: ${profile.description}`);
44
+ }
45
+ console.log(` Lane: ${profile.lane_id}`);
46
+ console.log(` Subject: ${profile.subject_mode || 'optional'}`);
47
+ console.log(` Output mode: ${profile.output_mode}`);
48
+ console.log(` Output count: ${profile.output_count}`);
49
+ console.log('');
50
+
51
+ console.log(' \x1b[1mPrompt strategy:\x1b[0m');
52
+ console.log(` Style prefix: ${profile.prompt_strategy.style_prefix.join(', ')}`);
53
+ console.log(` Structure: ${profile.prompt_strategy.structure}`);
54
+ console.log(` Must include: ${profile.prompt_strategy.must_include.join(', ')}`);
55
+ if (profile.prompt_strategy.optional_blocks?.length > 0) {
56
+ console.log(` Optional: ${profile.prompt_strategy.optional_blocks.join(', ')}`);
57
+ }
58
+ console.log('');
59
+
60
+ console.log(' \x1b[1mNegative strategy:\x1b[0m');
61
+ console.log(` Must avoid: ${profile.negative_strategy.must_avoid.join(', ')}`);
62
+ if (profile.negative_strategy.lane_specific_avoid?.length > 0) {
63
+ console.log(` Lane avoid: ${profile.negative_strategy.lane_specific_avoid.join(', ')}`);
64
+ }
65
+ console.log('');
66
+
67
+ console.log(' \x1b[1mCanon focus:\x1b[0m');
68
+ console.log(` ${profile.canon_focus.join(', ')}`);
69
+ console.log('');
70
+
71
+ console.log(' \x1b[1mDrift guards:\x1b[0m');
72
+ for (const guard of profile.drift_guards) {
73
+ console.log(` ⚠ ${guard}`);
74
+ }
75
+ console.log('');
76
+
77
+ console.log(' \x1b[1mRuntime defaults:\x1b[0m');
78
+ const rd = profile.runtime_defaults;
79
+ console.log(` Adapter: ${rd.adapter_target}`);
80
+ if (rd.width) console.log(` Size: ${rd.width}×${rd.height || rd.width}`);
81
+ if (rd.steps) console.log(` Steps: ${rd.steps}`);
82
+ if (rd.cfg) console.log(` CFG: ${rd.cfg}`);
83
+ if (rd.sampler) console.log(` Sampler: ${rd.sampler}`);
84
+ if (rd.seed_mode) console.log(` Seed mode: ${rd.seed_mode}`);
85
+
86
+ if (profile.required_reference_roles?.length > 0) {
87
+ console.log('');
88
+ console.log(` \x1b[1mRequired references:\x1b[0m ${profile.required_reference_roles.join(', ')}`);
89
+ }
90
+ if (profile.preferred_reference_tags?.length > 0) {
91
+ console.log(` \x1b[1mPreferred tags:\x1b[0m ${profile.preferred_reference_tags.join(', ')}`);
92
+ }
93
+ }
94
+
95
+ if (process.argv[1] && (process.argv[1].endsWith('workflow-show.js') || process.argv[1].endsWith('workflow-show'))) {
96
+ run().catch((err) => { console.error(err.message || err); process.exit(1); });
97
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "wave": "wave1",
3
+ "description": "Architecture — first wave. Subject IDs match this domain's lane id_patterns (ext_, int_, street_) so `sdlab generate` will route each candidate into the correct lane instead of default_lane.",
4
+ "defaults": {
5
+ "checkpoint": "dreamshaperXL_v21TurboDPMSDE.safetensors",
6
+ "lora": { "name": "classipeintxl_v21.safetensors", "weight": 1.0 },
7
+ "width": 1024,
8
+ "height": 1024,
9
+ "steps": 8,
10
+ "cfg": 2.0,
11
+ "sampler": "dpmpp_sde",
12
+ "scheduler": "karras",
13
+ "negative": "photorealistic, photograph, 3d render, smooth CG, sharp photo detail"
14
+ },
15
+ "subjects": [
16
+ {
17
+ "id": "ext_landmark",
18
+ "variations": 2,
19
+ "prompt": "oil painting, visible brushstrokes, [DESCRIBE YOUR BUILDING — facade, elevation, architectural massing], painterly concept art, canvas grain"
20
+ },
21
+ {
22
+ "id": "int_hall",
23
+ "variations": 2,
24
+ "prompt": "oil painting, visible brushstrokes, [DESCRIBE YOUR INTERIOR — hall, chamber, spatial atmosphere], painterly concept art, canvas grain"
25
+ },
26
+ {
27
+ "id": "street_district",
28
+ "variations": 2,
29
+ "prompt": "oil painting, visible brushstrokes, [DESCRIBE YOUR STREETSCAPE — street-level view, urban fabric, district overview], painterly concept art, canvas grain"
30
+ }
31
+ ]
32
+ }
@@ -8,12 +8,14 @@
8
8
  "null_faction_patterns": [],
9
9
  "id_fallbacks": {},
10
10
  "faction_context": {},
11
- "training_profiles": {
12
- "profile_id": "architecture-style",
13
- "intended_use": "Architectural style LoRA for consistent worldbuilding",
14
- "eligible_lanes": ["exterior", "interior", "streetscape"],
15
- "minimum_review_threshold": 0.7,
16
- "excluded_content": ["rejected"],
17
- "export_format": "trl"
18
- }
11
+ "training_profiles": [
12
+ {
13
+ "profile_id": "architecture-style",
14
+ "intended_use": "Architectural style LoRA for consistent worldbuilding",
15
+ "eligible_lanes": ["exterior", "interior", "streetscape"],
16
+ "minimum_review_threshold": 0.7,
17
+ "excluded_content": ["rejected"],
18
+ "export_format": "trl"
19
+ }
20
+ ]
19
21
  }
@@ -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
+ }