@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +108 -15
  2. package/CODE_OF_CONDUCT.md +9 -0
  3. package/CONTRIBUTING.md +71 -0
  4. package/README.es.md +99 -8
  5. package/README.fr.md +100 -9
  6. package/README.hi.md +102 -9
  7. package/README.it.md +99 -8
  8. package/README.ja.md +100 -9
  9. package/README.md +64 -1
  10. package/README.pt-BR.md +99 -8
  11. package/README.zh.md +100 -9
  12. package/SECURITY.md +6 -5
  13. package/bin/sdlab.js +289 -62
  14. package/lib/adapters/comfyui-runner.js +11 -15
  15. package/lib/adapters/diffusers-lora.js +7 -2
  16. package/lib/adapters/generic-image-caption.js +6 -1
  17. package/lib/args.js +200 -13
  18. package/lib/batch-compiler.js +7 -3
  19. package/lib/batch-runs.js +162 -31
  20. package/lib/batch-sheet-render.js +5 -2
  21. package/lib/brief-compiler.js +38 -11
  22. package/lib/card.js +11 -4
  23. package/lib/comfyui-output.js +70 -0
  24. package/lib/comfyui.js +100 -6
  25. package/lib/config.js +68 -3
  26. package/lib/deprecation.js +1 -1
  27. package/lib/eval-pack.js +94 -4
  28. package/lib/eval-runs.js +42 -3
  29. package/lib/export.js +144 -34
  30. package/lib/implementation-packs.js +15 -1
  31. package/lib/log.js +41 -4
  32. package/lib/paths.js +41 -3
  33. package/lib/refine-briefs.js +16 -9
  34. package/lib/reingest-selected.js +14 -3
  35. package/lib/reingest.js +15 -1
  36. package/lib/run-summary.js +3 -2
  37. package/lib/runtime-runs.js +59 -9
  38. package/lib/selections.js +54 -2
  39. package/lib/snapshot.js +100 -7
  40. package/lib/split.js +175 -17
  41. package/lib/training-adapters.js +79 -0
  42. package/lib/training-manifests.js +17 -3
  43. package/lib/training-packages.js +29 -28
  44. package/lib/training-profiles.js +10 -0
  45. package/package.json +27 -3
  46. package/scripts/batch-generate.js +72 -20
  47. package/scripts/batch-sheet.js +3 -3
  48. package/scripts/batch-show.js +3 -3
  49. package/scripts/brief-compile.js +8 -8
  50. package/scripts/brief-show.js +4 -3
  51. package/scripts/canon-bind.js +7 -9
  52. package/scripts/card.js +22 -25
  53. package/scripts/compare.js +38 -33
  54. package/scripts/critique-show.js +3 -3
  55. package/scripts/critique.js +3 -3
  56. package/scripts/curate.js +91 -46
  57. package/scripts/doctor.js +3 -5
  58. package/scripts/eligibility.js +17 -14
  59. package/scripts/eval-pack.js +13 -11
  60. package/scripts/eval-run.js +30 -29
  61. package/scripts/export.js +27 -31
  62. package/scripts/generate-controlnet.js +31 -17
  63. package/scripts/generate-identity.js +37 -20
  64. package/scripts/generate-ipadapter.js +22 -16
  65. package/scripts/generate.js +96 -29
  66. package/scripts/implementation-pack.js +19 -14
  67. package/scripts/init.js +115 -17
  68. package/scripts/painterly.js +8 -10
  69. package/scripts/refine.js +3 -3
  70. package/scripts/reingest-selected.js +3 -3
  71. package/scripts/reingest.js +33 -20
  72. package/scripts/run-generate.js +4 -4
  73. package/scripts/run-list.js +3 -3
  74. package/scripts/run-show.js +3 -3
  75. package/scripts/select.js +5 -4
  76. package/scripts/selection-show.js +3 -3
  77. package/scripts/snapshot.js +22 -24
  78. package/scripts/split.js +26 -31
  79. package/scripts/training-manifest.js +29 -24
  80. package/scripts/training-package.js +24 -20
  81. package/scripts/training-profile.js +13 -8
  82. package/scripts/workflow-list.js +2 -2
  83. package/scripts/workflow-show.js +4 -3
  84. package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
  85. package/templates/domains/architecture/terminology.json +10 -8
  86. package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
  87. package/templates/domains/character-design/project.json +4 -2
  88. package/templates/domains/creature-design/inputs/prompts/example-wave.json +32 -0
  89. package/templates/domains/creature-design/project.json +4 -2
  90. package/templates/domains/creature-design/terminology.json +1 -0
  91. package/templates/domains/game-art/inputs/prompts/example-wave.json +37 -0
  92. package/templates/domains/game-art/project.json +4 -2
  93. package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
  94. package/templates/domains/vehicle-mech/project.json +4 -2
  95. package/scripts/bulk-curate-wave2-5.js +0 -138
  96. package/scripts/bulk-curate-waves11-18.js +0 -413
  97. package/scripts/curate-wave25.js +0 -209
  98. package/scripts/migrate-records.js +0 -141
  99. package/templates/inputs/prompts/example-wave.json +0 -27
@@ -9,7 +9,7 @@
9
9
  * sdlab run generate --brief brief_2026-04-15_001 --project star-freight --seed 42
10
10
  */
11
11
 
12
- import { parseArgs } from '../lib/args.js';
12
+ import { parseArgs, getProjectName, parseNumberFlag } from '../lib/args.js';
13
13
  import { getProjectRoot, getRunsDir } from '../lib/paths.js';
14
14
  import { loadCompiledBrief } from '../lib/brief-compiler.js';
15
15
  import { executeRun } from '../lib/adapters/comfyui-runner.js';
@@ -20,7 +20,7 @@ import { join } from 'node:path';
20
20
  export async function run(argv = process.argv.slice(2)) {
21
21
  const { flags } = parseArgs(argv, {
22
22
  flags: {
23
- project: { type: 'string', default: 'star-freight' },
23
+ project: { type: 'string' },
24
24
  brief: { type: 'string' },
25
25
  seed: { type: 'string' },
26
26
  'dry-run': { type: 'boolean' },
@@ -40,7 +40,7 @@ export async function run(argv = process.argv.slice(2)) {
40
40
  return;
41
41
  }
42
42
 
43
- const projectName = flags.project;
43
+ const projectName = flags.project || getProjectName(argv);
44
44
  const projectRoot = getProjectRoot(projectName);
45
45
 
46
46
  console.log(`\x1b[1mstyle-dataset-lab\x1b[0m run generate`);
@@ -57,7 +57,7 @@ export async function run(argv = process.argv.slice(2)) {
57
57
  projectRoot,
58
58
  projectName,
59
59
  brief,
60
- baseSeed: flags.seed ? parseInt(flags.seed, 10) : undefined,
60
+ baseSeed: flags.seed ? parseNumberFlag('seed', flags.seed, { int: true, min: 0 }) : undefined,
61
61
  dryRun: flags['dry-run'] || false,
62
62
  });
63
63
 
@@ -8,20 +8,20 @@
8
8
  * sdlab run list --project star-freight --json
9
9
  */
10
10
 
11
- import { parseArgs } from '../lib/args.js';
11
+ import { parseArgs, getProjectName } from '../lib/args.js';
12
12
  import { getProjectRoot } from '../lib/paths.js';
13
13
  import { listRuns } from '../lib/runtime-runs.js';
14
14
 
15
15
  export async function run(argv = process.argv.slice(2)) {
16
16
  const { flags } = parseArgs(argv, {
17
17
  flags: {
18
- project: { type: 'string', default: 'star-freight' },
18
+ project: { type: 'string' },
19
19
  json: { type: 'boolean' },
20
20
  },
21
21
  deprecated: { game: 'project' },
22
22
  });
23
23
 
24
- const projectName = flags.project;
24
+ const projectName = flags.project || getProjectName(argv);
25
25
  const projectRoot = getProjectRoot(projectName);
26
26
  const runs = listRuns(projectRoot);
27
27
 
@@ -9,7 +9,7 @@
9
9
  * sdlab run show run_2026-04-16_001 --project star-freight --md
10
10
  */
11
11
 
12
- import { parseArgs } from '../lib/args.js';
12
+ import { parseArgs, getProjectName } from '../lib/args.js';
13
13
  import { getProjectRoot } from '../lib/paths.js';
14
14
  import { loadRun, listRuns } from '../lib/runtime-runs.js';
15
15
  import { renderRunText, renderRunMarkdown } from '../lib/run-summary.js';
@@ -17,7 +17,7 @@ import { renderRunText, renderRunMarkdown } from '../lib/run-summary.js';
17
17
  export async function run(argv = process.argv.slice(2)) {
18
18
  const { positionals, flags } = parseArgs(argv, {
19
19
  flags: {
20
- project: { type: 'string', default: 'star-freight' },
20
+ project: { type: 'string' },
21
21
  json: { type: 'boolean' },
22
22
  md: { type: 'boolean' },
23
23
  },
@@ -25,7 +25,7 @@ export async function run(argv = process.argv.slice(2)) {
25
25
  });
26
26
 
27
27
  const runId = positionals[0];
28
- const projectName = flags.project;
28
+ const projectName = flags.project || getProjectName(argv);
29
29
  const projectRoot = getProjectRoot(projectName);
30
30
 
31
31
  // No run ID → list runs
package/scripts/select.js CHANGED
@@ -9,8 +9,9 @@
9
9
  * sdlab select --run run_2026-04-15_001 --approve 001.png --reason "best continuity" --tags portrait,keeper
10
10
  */
11
11
 
12
- import { parseArgs } from '../lib/args.js';
12
+ import { parseArgs, getProjectName } from '../lib/args.js';
13
13
  import { getProjectRoot } from '../lib/paths.js';
14
+ import { inputError, handleCliError } from '../lib/errors.js';
14
15
  import {
15
16
  createSelectionFromRun,
16
17
  createSelectionFromBatch,
@@ -21,7 +22,7 @@ import { info } from '../lib/log.js';
21
22
  export async function run(argv = process.argv.slice(2)) {
22
23
  const { flags } = parseArgs(argv, {
23
24
  flags: {
24
- project: { type: 'string', default: 'star-freight' },
25
+ project: { type: 'string' },
25
26
  run: { type: 'string' },
26
27
  batch: { type: 'string' },
27
28
  approve: { type: 'string' },
@@ -46,7 +47,7 @@ export async function run(argv = process.argv.slice(2)) {
46
47
  return;
47
48
  }
48
49
 
49
- const projectName = flags.project;
50
+ const projectName = flags.project || getProjectName(argv);
50
51
  const projectRoot = getProjectRoot(projectName);
51
52
  const reason = flags.reason || undefined;
52
53
  const tags = flags.tags ? flags.tags.split(',').map(t => t.trim()) : undefined;
@@ -71,7 +72,7 @@ export async function run(argv = process.argv.slice(2)) {
71
72
  const approvedSlots = pairs.map(pair => {
72
73
  const colonIdx = pair.indexOf(':');
73
74
  if (colonIdx < 0) {
74
- throw new Error(`Invalid batch approve format: "${pair}". Use slot_id:filename`);
75
+ throw inputError('INPUT_BAD_FORMAT', `Invalid batch approve format: "${pair}". Use slot_id:filename`);
75
76
  }
76
77
  return {
77
78
  slotId: pair.slice(0, colonIdx),
@@ -9,7 +9,7 @@
9
9
  * sdlab selection show selection_2026-04-16_001 --json
10
10
  */
11
11
 
12
- import { parseArgs } from '../lib/args.js';
12
+ import { parseArgs, getProjectName } from '../lib/args.js';
13
13
  import { getProjectRoot } from '../lib/paths.js';
14
14
  import { loadSelection, listSelections, getSelectionsDir } from '../lib/selections.js';
15
15
  import { join } from 'node:path';
@@ -18,14 +18,14 @@ import { existsSync, readFileSync } from 'node:fs';
18
18
  export async function run(argv = process.argv.slice(2)) {
19
19
  const { flags, positionals } = parseArgs(argv, {
20
20
  flags: {
21
- project: { type: 'string', default: 'star-freight' },
21
+ project: { type: 'string' },
22
22
  selection: { type: 'string' },
23
23
  json: { type: 'boolean' },
24
24
  },
25
25
  deprecated: { game: 'project' },
26
26
  });
27
27
 
28
- const projectName = flags.project;
28
+ const projectName = flags.project || getProjectName(argv);
29
29
  const projectRoot = getProjectRoot(projectName);
30
30
  const selectionId = positionals[0] || flags.selection;
31
31
 
@@ -12,30 +12,33 @@
12
12
 
13
13
  import { readFile } from 'node:fs/promises';
14
14
  import { join } from 'node:path';
15
- import { existsSync } from 'node:fs';
16
- import { getProjectName } from '../lib/args.js';
15
+ import { parseArgs, getProjectName } from '../lib/args.js';
17
16
  import { REPO_ROOT } from '../lib/paths.js';
18
17
  import { loadSelectionProfile } from '../lib/config.js';
18
+ import { inputError, handleCliError } from '../lib/errors.js';
19
19
  import { createSnapshot, loadSnapshot, listSnapshots, diffSnapshots } from '../lib/snapshot.js';
20
- import { evaluateEligibility } from '../lib/eligibility.js';
21
20
  import { categorizeExclusions } from '../lib/eligibility.js';
22
21
 
23
22
  export async function run(argv = process.argv.slice(2)) {
24
- const projectName = getProjectName(argv);
25
- const projectRoot = join(REPO_ROOT, 'projects', projectName);
26
- const subcommand = argv.find(a => !a.startsWith('--')) || 'list';
23
+ const { flags, positionals } = parseArgs(argv, {
24
+ flags: {
25
+ project: { type: 'string' },
26
+ profile: { type: 'string' },
27
+ 'dry-run': { type: 'boolean', default: false },
28
+ },
29
+ deprecated: { game: 'project' },
30
+ });
27
31
 
28
- // Strip subcommand from argv for flag parsing
29
- const args = argv.filter(a => a !== subcommand);
32
+ const projectName = flags.project || getProjectName(argv);
33
+ const projectRoot = join(REPO_ROOT, 'projects', projectName);
34
+ const subcommand = positionals[0] || 'list';
35
+ const dryRun = flags['dry-run'] || flags.dryRun;
30
36
 
31
37
  if (subcommand === 'create') {
32
- const profileIdx = args.indexOf('--profile');
33
- const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
34
- const profile = loadSelectionProfile(projectRoot, profileId);
35
- const dryRun = argv.includes('--dry-run');
38
+ const profile = loadSelectionProfile(projectRoot, flags.profile);
36
39
 
37
40
  console.log(`\x1b[1msdlab snapshot create\x1b[0m — ${projectName}`);
38
- console.log(` Profile: ${profileId || 'training-default (built-in)'}`);
41
+ console.log(` Profile: ${flags.profile || 'training-default (built-in)'}`);
39
42
  if (dryRun) console.log(' Mode: DRY RUN (no files written)');
40
43
  console.log('');
41
44
 
@@ -46,7 +49,6 @@ export async function run(argv = process.argv.slice(2)) {
46
49
  console.log(` Excluded: ${result.excluded}`);
47
50
 
48
51
  if (!dryRun) {
49
- // Read and display summary
50
52
  const summary = JSON.parse(
51
53
  await readFile(join(projectRoot, 'snapshots', result.snapshotId, 'summary.json'), 'utf-8')
52
54
  );
@@ -59,7 +61,6 @@ export async function run(argv = process.argv.slice(2)) {
59
61
  console.log(` ${f}: ${c}`);
60
62
  }
61
63
 
62
- // Read and categorize exclusions
63
64
  const excludedRaw = await readFile(
64
65
  join(projectRoot, 'snapshots', result.snapshotId, 'excluded.jsonl'), 'utf-8'
65
66
  );
@@ -83,8 +84,8 @@ export async function run(argv = process.argv.slice(2)) {
83
84
  }
84
85
 
85
86
  } else if (subcommand === 'show') {
86
- const snapshotId = args.find(a => a.startsWith('snap-'));
87
- if (!snapshotId) throw new Error('Usage: sdlab snapshot show <snapshot-id>');
87
+ const snapshotId = positionals[1] || positionals.find(a => a.startsWith('snap-'));
88
+ if (!snapshotId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab snapshot show <snapshot-id>');
88
89
 
89
90
  const manifest = await loadSnapshot(projectRoot, snapshotId);
90
91
  console.log(`\x1b[1mSnapshot\x1b[0m — ${snapshotId}\n`);
@@ -102,8 +103,8 @@ export async function run(argv = process.argv.slice(2)) {
102
103
  }
103
104
 
104
105
  } else if (subcommand === 'diff') {
105
- const snapIds = args.filter(a => a.startsWith('snap-'));
106
- if (snapIds.length < 2) throw new Error('Usage: sdlab snapshot diff <id-a> <id-b>');
106
+ const snapIds = positionals.slice(1).filter(a => a.startsWith('snap-'));
107
+ if (snapIds.length < 2) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab snapshot diff <id-a> <id-b>');
107
108
 
108
109
  const diff = await diffSnapshots(projectRoot, snapIds[0], snapIds[1]);
109
110
  console.log(`\x1b[1mSnapshot diff\x1b[0m — ${snapIds[0]} vs ${snapIds[1]}\n`);
@@ -123,14 +124,11 @@ export async function run(argv = process.argv.slice(2)) {
123
124
  }
124
125
 
125
126
  } else {
126
- throw new Error(`Unknown subcommand: ${subcommand}. Use: create, list, show, diff`);
127
+ throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: create, list, show, diff`);
127
128
  }
128
129
  }
129
130
 
130
131
  // Direct execution guard
131
132
  if (process.argv[1] && (process.argv[1].endsWith('snapshot.js') || process.argv[1].endsWith('snapshot'))) {
132
- run().catch((err) => {
133
- console.error(err.message || err);
134
- process.exit(1);
135
- });
133
+ run().catch(handleCliError);
136
134
  }
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
  }
@@ -15,13 +15,13 @@ import { listWorkflowProfiles } from '../lib/workflow-profiles.js';
15
15
  export async function run(argv = process.argv.slice(2)) {
16
16
  const { flags } = parseArgs(argv, {
17
17
  flags: {
18
- project: { type: 'string', default: 'star-freight' },
18
+ project: { type: 'string' },
19
19
  json: { type: 'boolean' },
20
20
  },
21
21
  deprecated: { game: 'project' },
22
22
  });
23
23
 
24
- const projectName = flags.project;
24
+ const projectName = flags.project || getProjectName(argv);
25
25
  const projectRoot = getProjectRoot(projectName);
26
26
  const profiles = await listWorkflowProfiles(projectRoot);
27
27
 
@@ -8,14 +8,14 @@
8
8
  * sdlab workflow show character-portrait-set --json
9
9
  */
10
10
 
11
- import { parseArgs } from '../lib/args.js';
11
+ import { parseArgs, getProjectName } from '../lib/args.js';
12
12
  import { getProjectRoot } from '../lib/paths.js';
13
13
  import { getWorkflowProfile } from '../lib/workflow-profiles.js';
14
14
 
15
15
  export async function run(argv = process.argv.slice(2)) {
16
16
  const { flags, positionals } = parseArgs(argv, {
17
17
  flags: {
18
- project: { type: 'string', default: 'star-freight' },
18
+ project: { type: 'string' },
19
19
  json: { type: 'boolean' },
20
20
  },
21
21
  positionals: ['workflowId'],
@@ -28,7 +28,8 @@ export async function run(argv = process.argv.slice(2)) {
28
28
  return;
29
29
  }
30
30
 
31
- const projectRoot = getProjectRoot(flags.project);
31
+ const projectName = flags.project || getProjectName(argv);
32
+ const projectRoot = getProjectRoot(projectName);
32
33
  const profile = await getWorkflowProfile(projectRoot, workflowId);
33
34
 
34
35
  if (flags.json) {