@mcptoolshop/style-dataset-lab 3.0.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.
- package/CHANGELOG.md +68 -15
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +71 -0
- package/README.md +46 -1
- package/SECURITY.md +6 -5
- package/bin/sdlab.js +281 -62
- package/lib/adapters/diffusers-lora.js +7 -2
- package/lib/adapters/generic-image-caption.js +6 -1
- package/lib/args.js +200 -13
- package/lib/batch-compiler.js +7 -3
- package/lib/batch-runs.js +100 -31
- package/lib/batch-sheet-render.js +5 -2
- package/lib/brief-compiler.js +38 -11
- package/lib/card.js +11 -4
- package/lib/comfyui.js +100 -6
- package/lib/config.js +68 -3
- package/lib/deprecation.js +1 -1
- package/lib/eval-pack.js +94 -4
- package/lib/eval-runs.js +42 -3
- package/lib/export.js +144 -34
- package/lib/implementation-packs.js +15 -1
- package/lib/log.js +41 -4
- package/lib/paths.js +41 -3
- package/lib/refine-briefs.js +16 -9
- package/lib/reingest-selected.js +14 -3
- package/lib/reingest.js +15 -1
- package/lib/run-summary.js +3 -2
- package/lib/runtime-runs.js +59 -9
- package/lib/selections.js +54 -2
- package/lib/snapshot.js +100 -7
- package/lib/split.js +175 -17
- package/lib/training-manifests.js +17 -3
- package/lib/training-packages.js +28 -14
- package/package.json +8 -2
- package/scripts/batch-generate.js +16 -7
- package/scripts/batch-sheet.js +3 -3
- package/scripts/batch-show.js +3 -3
- package/scripts/brief-compile.js +8 -8
- package/scripts/brief-show.js +4 -3
- package/scripts/canon-bind.js +7 -9
- package/scripts/card.js +22 -25
- package/scripts/compare.js +38 -33
- package/scripts/critique-show.js +3 -3
- package/scripts/critique.js +3 -3
- package/scripts/curate.js +91 -46
- package/scripts/doctor.js +3 -5
- package/scripts/eligibility.js +17 -14
- package/scripts/eval-pack.js +13 -11
- package/scripts/eval-run.js +30 -29
- package/scripts/export.js +27 -31
- package/scripts/generate-controlnet.js +31 -17
- package/scripts/generate-identity.js +37 -20
- package/scripts/generate-ipadapter.js +22 -16
- package/scripts/generate.js +68 -13
- package/scripts/implementation-pack.js +19 -14
- package/scripts/init.js +115 -17
- package/scripts/painterly.js +8 -10
- package/scripts/refine.js +3 -3
- package/scripts/reingest-selected.js +3 -3
- package/scripts/reingest.js +33 -20
- package/scripts/run-generate.js +4 -4
- package/scripts/run-list.js +3 -3
- package/scripts/run-show.js +3 -3
- package/scripts/select.js +5 -4
- package/scripts/selection-show.js +3 -3
- package/scripts/snapshot.js +22 -24
- package/scripts/split.js +26 -31
- package/scripts/training-manifest.js +29 -24
- package/scripts/training-package.js +24 -20
- package/scripts/training-profile.js +13 -8
- package/scripts/workflow-list.js +2 -2
- package/scripts/workflow-show.js +4 -3
- package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/architecture/terminology.json +10 -8
- package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/character-design/project.json +4 -2
- package/templates/domains/creature-design/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/creature-design/project.json +4 -2
- package/templates/domains/creature-design/terminology.json +1 -0
- package/templates/domains/game-art/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/game-art/project.json +4 -2
- package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/vehicle-mech/project.json +4 -2
- package/scripts/bulk-curate-wave2-5.js +0 -138
- package/scripts/bulk-curate-waves11-18.js +0 -413
- package/scripts/curate-wave25.js +0 -209
- package/scripts/migrate-records.js +0 -141
- package/templates/inputs/prompts/example-wave.json +0 -27
package/bin/sdlab.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { enableDebug, handleCliError, inputError } from '../lib/errors.js';
|
|
11
11
|
import { setLogLevel } from '../lib/log.js';
|
|
12
|
+
import { findClosest } from '../lib/args.js';
|
|
12
13
|
|
|
13
14
|
const COMMANDS = {
|
|
14
15
|
'init': '../scripts/init.js',
|
|
@@ -79,6 +80,204 @@ const SELECTION_COMMANDS = {
|
|
|
79
80
|
'show': '../scripts/selection-show.js',
|
|
80
81
|
};
|
|
81
82
|
|
|
83
|
+
// ─── Per-command help ─────────────────────────────────────────────────
|
|
84
|
+
//
|
|
85
|
+
// Keyed by the user-facing command (including two-word forms joined by
|
|
86
|
+
// a space). Help text is plain — no color codes — to stay readable in
|
|
87
|
+
// dumb terminals and when piped to files.
|
|
88
|
+
|
|
89
|
+
const HELP_TEXT = {
|
|
90
|
+
'init': `sdlab init <project-name> [--domain <domain>]
|
|
91
|
+
|
|
92
|
+
Scaffold a new project from a domain template.
|
|
93
|
+
|
|
94
|
+
Positional:
|
|
95
|
+
project-name Lowercase, hyphen-separated (e.g. "my-project")
|
|
96
|
+
|
|
97
|
+
Flags:
|
|
98
|
+
--domain <id> Domain template (default: generic)
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
sdlab init my-project
|
|
102
|
+
sdlab init heist-crew --domain character-design
|
|
103
|
+
|
|
104
|
+
Run "sdlab init" with no arguments to see the available domains.`,
|
|
105
|
+
|
|
106
|
+
'project doctor': `sdlab project doctor --project <name>
|
|
107
|
+
|
|
108
|
+
Validate project config and directory structure. Reports missing
|
|
109
|
+
required files, unmigrated record schemas, and orphaned outputs.
|
|
110
|
+
|
|
111
|
+
Flags:
|
|
112
|
+
--project <name> Project to audit (required in practice)
|
|
113
|
+
--verbose Show per-check detail
|
|
114
|
+
--quiet Only print PASS / FAIL summary`,
|
|
115
|
+
|
|
116
|
+
'generate': `sdlab generate [<pack-path>] --project <name> [--dry-run]
|
|
117
|
+
|
|
118
|
+
Generate candidates from a prompt pack (JSON file under
|
|
119
|
+
projects/<name>/inputs/prompts/).
|
|
120
|
+
|
|
121
|
+
Positional:
|
|
122
|
+
pack-path Path to the prompt pack (default: inputs/prompts/rpg-icons-lane1.json)
|
|
123
|
+
|
|
124
|
+
Flags:
|
|
125
|
+
--project <name> Project to operate on
|
|
126
|
+
--dry-run Preview work without contacting ComfyUI
|
|
127
|
+
|
|
128
|
+
Env:
|
|
129
|
+
COMFY_URL ComfyUI HTTP endpoint (default: http://127.0.0.1:8188)
|
|
130
|
+
|
|
131
|
+
Precedence: CLI flag > env COMFY_URL > project defaults > built-in default.`,
|
|
132
|
+
|
|
133
|
+
'batch generate': `sdlab batch generate --mode <id> [--subject <id>] [--theme <label>] --project <name>
|
|
134
|
+
|
|
135
|
+
Execute a batch production mode (expression sheet, silhouette pack,
|
|
136
|
+
environment board, etc).
|
|
137
|
+
|
|
138
|
+
Flags:
|
|
139
|
+
--mode <id> Batch mode id (required)
|
|
140
|
+
--subject <id> Subject for subject-driven modes
|
|
141
|
+
--theme <label> Theme label for environment modes
|
|
142
|
+
--asset <id> Training asset reference
|
|
143
|
+
--project <name> Project to operate on
|
|
144
|
+
--dry-run Prepare batch without submitting to ComfyUI
|
|
145
|
+
--json Output manifest as JSON`,
|
|
146
|
+
|
|
147
|
+
'brief compile': `sdlab brief compile --profile <id> --project <name>
|
|
148
|
+
|
|
149
|
+
Compile a generation brief from project canon (constitution, lanes,
|
|
150
|
+
workflow profile).
|
|
151
|
+
|
|
152
|
+
Flags:
|
|
153
|
+
--profile <id> Workflow profile id to compile against
|
|
154
|
+
--project <name> Project to operate on
|
|
155
|
+
--out <path> Optional explicit output path`,
|
|
156
|
+
|
|
157
|
+
'run generate': `sdlab run generate --brief <id> --project <name> [--dry-run]
|
|
158
|
+
|
|
159
|
+
Execute a compiled brief through ComfyUI.
|
|
160
|
+
|
|
161
|
+
Flags:
|
|
162
|
+
--brief <id> Brief id to execute (required)
|
|
163
|
+
--project <name> Project to operate on
|
|
164
|
+
--dry-run Plan without submitting to ComfyUI`,
|
|
165
|
+
|
|
166
|
+
'critique': `sdlab critique --run <id> --project <name>
|
|
167
|
+
|
|
168
|
+
Critique a completed run. Writes a critique record alongside the run.
|
|
169
|
+
|
|
170
|
+
Flags:
|
|
171
|
+
--run <id> Run id to critique (required)
|
|
172
|
+
--project <name> Project to operate on
|
|
173
|
+
--json Emit critique as JSON on stdout`,
|
|
174
|
+
|
|
175
|
+
'refine': `sdlab refine --run <id> --pick <file> --project <name>
|
|
176
|
+
|
|
177
|
+
Generate a refined next-pass brief based on a specific pick from a
|
|
178
|
+
prior run.
|
|
179
|
+
|
|
180
|
+
Flags:
|
|
181
|
+
--run <id> Source run id (required)
|
|
182
|
+
--pick <file> Picked output filename inside the run (required)
|
|
183
|
+
--project <name> Project to operate on`,
|
|
184
|
+
|
|
185
|
+
'batch sheet': `sdlab batch sheet <batch-id> --project <name>
|
|
186
|
+
|
|
187
|
+
Re-render a batch's contact sheet from its manifest.
|
|
188
|
+
|
|
189
|
+
Positional:
|
|
190
|
+
batch-id Batch id to re-render
|
|
191
|
+
|
|
192
|
+
Flags:
|
|
193
|
+
--project <name> Project to operate on`,
|
|
194
|
+
|
|
195
|
+
'select': `sdlab select [--run <id>|--batch <id>] --approve <files> --project <name>
|
|
196
|
+
|
|
197
|
+
Select approved outputs from a run or batch.
|
|
198
|
+
|
|
199
|
+
Flags:
|
|
200
|
+
--run <id> Source run id
|
|
201
|
+
--batch <id> Source batch id
|
|
202
|
+
--approve <files> Comma-separated output filenames to approve
|
|
203
|
+
--project <name> Project to operate on`,
|
|
204
|
+
|
|
205
|
+
'curate': `sdlab curate <asset_id> <approved|rejected|borderline> <explanation> [--scores k:v,...] [--failures f1,f2] --project <name>
|
|
206
|
+
|
|
207
|
+
Record a judgment on a candidate and move it to the matching outputs
|
|
208
|
+
subdirectory.
|
|
209
|
+
|
|
210
|
+
Positional:
|
|
211
|
+
asset_id Candidate asset id
|
|
212
|
+
status approved | rejected | borderline
|
|
213
|
+
explanation Short reason (quoted)
|
|
214
|
+
|
|
215
|
+
Flags:
|
|
216
|
+
--scores k:v,... Per-dimension scores (e.g. silhouette:0.9,palette:0.8)
|
|
217
|
+
--failures f1,f2 Named failure tags (comma-separated)
|
|
218
|
+
--notes "…" Optional free-form note
|
|
219
|
+
--list Print uncurated candidates and exit
|
|
220
|
+
--dry-run Preview without moving files
|
|
221
|
+
--project <name> Project to operate on`,
|
|
222
|
+
|
|
223
|
+
'canon-bind': `sdlab canon-bind --project <name> [--lane <id>]
|
|
224
|
+
|
|
225
|
+
Bind approved records to constitution rules.
|
|
226
|
+
|
|
227
|
+
Flags:
|
|
228
|
+
--project <name> Project to operate on
|
|
229
|
+
--lane <id> Restrict binding to a single lane`,
|
|
230
|
+
|
|
231
|
+
'snapshot': `sdlab snapshot <create|list|show|diff> [args] --project <name>
|
|
232
|
+
|
|
233
|
+
Manage frozen dataset snapshots.
|
|
234
|
+
|
|
235
|
+
Subcommands:
|
|
236
|
+
create Create a new snapshot from approved records
|
|
237
|
+
list List all snapshots (default subcommand)
|
|
238
|
+
show <id> Show snapshot details
|
|
239
|
+
diff <a> <b> Compare two snapshots
|
|
240
|
+
|
|
241
|
+
Flags:
|
|
242
|
+
--profile <name> Selection profile (create only)
|
|
243
|
+
--project <name> Project to operate on
|
|
244
|
+
--dry-run Preview snapshot contents without writing`,
|
|
245
|
+
|
|
246
|
+
'split': `sdlab split <build|list|show|audit> [args] --project <name>
|
|
247
|
+
|
|
248
|
+
Manage train/val/test splits from a snapshot.
|
|
249
|
+
|
|
250
|
+
Subcommands:
|
|
251
|
+
build Build a split from a snapshot
|
|
252
|
+
list List all splits
|
|
253
|
+
show <id> Show split details
|
|
254
|
+
audit <id> Audit a split for leakage and balance
|
|
255
|
+
|
|
256
|
+
Flags:
|
|
257
|
+
--project <name> Project to operate on`,
|
|
258
|
+
|
|
259
|
+
'export': `sdlab export <build|list> [args] --project <name>
|
|
260
|
+
|
|
261
|
+
Build and list self-contained export packages.
|
|
262
|
+
|
|
263
|
+
Subcommands:
|
|
264
|
+
build Build a new export package
|
|
265
|
+
list List all export packages
|
|
266
|
+
|
|
267
|
+
Flags:
|
|
268
|
+
--project <name> Project to operate on`,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// "bind" is a short alias for "canon-bind" — share help.
|
|
272
|
+
HELP_TEXT['bind'] = HELP_TEXT['canon-bind'];
|
|
273
|
+
|
|
274
|
+
function printCommandHelp(key) {
|
|
275
|
+
const text = HELP_TEXT[key];
|
|
276
|
+
if (!text) return false;
|
|
277
|
+
console.log(text);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
82
281
|
function printHelp() {
|
|
83
282
|
console.log(`\x1b[1msdlab\x1b[0m — Style Dataset Lab CLI\n`);
|
|
84
283
|
console.log('Usage: sdlab <command> [options]\n');
|
|
@@ -94,9 +293,10 @@ function printHelp() {
|
|
|
94
293
|
console.log(' generate:ipadapter IP-Adapter reference-guided generation');
|
|
95
294
|
console.log(' curate Move candidate to approved/rejected/borderline');
|
|
96
295
|
console.log(' compare Record pairwise A-vs-B comparison');
|
|
97
|
-
console.log(' bind
|
|
296
|
+
console.log(' canon-bind Bind approved records to constitution rules');
|
|
297
|
+
console.log(' ("bind" is a short alias for canon-bind)');
|
|
98
298
|
console.log(' painterly Post-processing painterly style pass');
|
|
99
|
-
console.log(' painterly:test
|
|
299
|
+
console.log(' painterly:test Calibrate painterly denoise level on sample images');
|
|
100
300
|
console.log('');
|
|
101
301
|
console.log('Dataset:');
|
|
102
302
|
console.log(' snapshot create Create a frozen dataset snapshot');
|
|
@@ -132,7 +332,8 @@ function printHelp() {
|
|
|
132
332
|
console.log(' implementation-pack build Build implementation example pack');
|
|
133
333
|
console.log(' implementation-pack show <id> Show pack details');
|
|
134
334
|
console.log(' implementation-pack list List all packs');
|
|
135
|
-
console.log(' reingest generated --source <dir> Re-ingest generated outputs');
|
|
335
|
+
console.log(' reingest generated --source <dir> --manifest <id> Re-ingest generated outputs');
|
|
336
|
+
console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
|
|
136
337
|
console.log(' reingest audit Audit re-ingested record status');
|
|
137
338
|
console.log('');
|
|
138
339
|
console.log('Production:');
|
|
@@ -155,22 +356,59 @@ function printHelp() {
|
|
|
155
356
|
console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
|
|
156
357
|
console.log('');
|
|
157
358
|
console.log('Options:');
|
|
158
|
-
console.log(' --project <name> Project to operate on (default
|
|
359
|
+
console.log(' --project <name> Project to operate on (no default — fallback to "star-freight" with warning)');
|
|
159
360
|
console.log(' --game <name> Deprecated alias for --project');
|
|
160
361
|
console.log(' --debug Show stack traces on error');
|
|
161
362
|
console.log(' --verbose Verbose output');
|
|
162
363
|
console.log(' --quiet Suppress non-essential output');
|
|
163
364
|
console.log(' --dry-run Preview changes without writing (where supported)');
|
|
164
|
-
console.log(' --help Show this help');
|
|
365
|
+
console.log(' --help Show this help (or per-command help with "sdlab <cmd> --help")');
|
|
165
366
|
console.log('');
|
|
166
367
|
console.log('Examples:');
|
|
167
368
|
console.log(' sdlab init my-project --domain character-design');
|
|
369
|
+
console.log(' sdlab generate --help');
|
|
168
370
|
console.log(' sdlab snapshot create --project star-freight');
|
|
169
371
|
console.log(' sdlab split build --dry-run --project star-freight');
|
|
170
372
|
console.log(' sdlab training-package build --debug --project star-freight');
|
|
171
373
|
}
|
|
172
374
|
|
|
375
|
+
// All top-level command tokens users can type (including two-word heads).
|
|
376
|
+
function allKnownCommands() {
|
|
377
|
+
const oneWord = Object.keys(COMMANDS);
|
|
378
|
+
const heads = ['project', 'workflow', 'brief', 'run', 'critique', 'refine', 'batch', 'select', 'selection'];
|
|
379
|
+
return [...new Set([...oneWord, ...heads])];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function argvHasHelp(argv) {
|
|
383
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── Signal + fatal-error handlers ────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
function installProcessHandlers() {
|
|
389
|
+
const onSignal = (sig) => {
|
|
390
|
+
const code = sig === 'SIGINT' ? 130 : 143;
|
|
391
|
+
process.stderr.write(`\n\x1b[31m✗ Interrupted by ${sig}. Partial output may exist.\x1b[0m\n`);
|
|
392
|
+
process.exit(code);
|
|
393
|
+
};
|
|
394
|
+
process.on('SIGINT', () => onSignal('SIGINT'));
|
|
395
|
+
process.on('SIGTERM', () => onSignal('SIGTERM'));
|
|
396
|
+
|
|
397
|
+
process.on('uncaughtException', (err) => {
|
|
398
|
+
handleCliError(err);
|
|
399
|
+
// handleCliError exits, but guard anyway.
|
|
400
|
+
process.exit(2);
|
|
401
|
+
});
|
|
402
|
+
process.on('unhandledRejection', (reason) => {
|
|
403
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
404
|
+
handleCliError(err);
|
|
405
|
+
process.exit(2);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
173
409
|
async function main() {
|
|
410
|
+
installProcessHandlers();
|
|
411
|
+
|
|
174
412
|
const args = process.argv.slice(2);
|
|
175
413
|
|
|
176
414
|
// Global flags
|
|
@@ -186,94 +424,75 @@ async function main() {
|
|
|
186
424
|
let command = args[0];
|
|
187
425
|
let commandArgs = args.slice(1);
|
|
188
426
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const modulePath = PROJECT_COMMANDS[args[1]];
|
|
193
|
-
const mod = await import(modulePath);
|
|
194
|
-
await mod.run(commandArgs);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Handle "workflow <subcommand>" two-word form
|
|
199
|
-
if (command === 'workflow' && args[1] && WORKFLOW_COMMANDS[args[1]]) {
|
|
200
|
-
commandArgs = args.slice(2);
|
|
201
|
-
const modulePath = WORKFLOW_COMMANDS[args[1]];
|
|
202
|
-
const mod = await import(modulePath);
|
|
203
|
-
await mod.run(commandArgs);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
427
|
+
// ── Two-word dispatch tables and --help interception ──
|
|
428
|
+
// For every two-word namespace, we check "cmd sub --help" first,
|
|
429
|
+
// then the single-word "cmd --help", then fall through to dispatch.
|
|
206
430
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
431
|
+
const twoWord = (head, table) => ({ head, table });
|
|
432
|
+
const namespaces = [
|
|
433
|
+
twoWord('project', PROJECT_COMMANDS),
|
|
434
|
+
twoWord('workflow', WORKFLOW_COMMANDS),
|
|
435
|
+
twoWord('brief', BRIEF_COMMANDS),
|
|
436
|
+
twoWord('run', RUN_COMMANDS),
|
|
437
|
+
twoWord('batch', BATCH_COMMANDS),
|
|
438
|
+
twoWord('selection', SELECTION_COMMANDS),
|
|
439
|
+
];
|
|
215
440
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
441
|
+
// "project", "workflow", "brief", "run", "batch", "selection" two-word forms
|
|
442
|
+
for (const { head, table } of namespaces) {
|
|
443
|
+
if (command === head && args[1] && table[args[1]]) {
|
|
444
|
+
commandArgs = args.slice(2);
|
|
445
|
+
const twoWordKey = `${head} ${args[1]}`;
|
|
446
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(twoWordKey)) return;
|
|
447
|
+
const mod = await import(table[args[1]]);
|
|
448
|
+
await mod.run(commandArgs);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
223
451
|
}
|
|
224
452
|
|
|
225
|
-
//
|
|
453
|
+
// "critique" — bare or "critique show"
|
|
226
454
|
if (command === 'critique') {
|
|
227
455
|
if (args[1] && CRITIQUE_COMMANDS[args[1]]) {
|
|
228
456
|
commandArgs = args.slice(2);
|
|
229
|
-
|
|
230
|
-
const mod = await import(
|
|
457
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(`critique ${args[1]}`)) return;
|
|
458
|
+
const mod = await import(CRITIQUE_COMMANDS[args[1]]);
|
|
231
459
|
await mod.run(commandArgs);
|
|
232
460
|
return;
|
|
233
461
|
}
|
|
234
|
-
|
|
462
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('critique')) return;
|
|
235
463
|
const mod = await import('../scripts/critique.js');
|
|
236
464
|
await mod.run(commandArgs);
|
|
237
465
|
return;
|
|
238
466
|
}
|
|
239
467
|
|
|
240
|
-
//
|
|
468
|
+
// "refine" — single command
|
|
241
469
|
if (command === 'refine') {
|
|
470
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('refine')) return;
|
|
242
471
|
const mod = await import('../scripts/refine.js');
|
|
243
472
|
await mod.run(commandArgs);
|
|
244
473
|
return;
|
|
245
474
|
}
|
|
246
475
|
|
|
247
|
-
//
|
|
248
|
-
if (command === 'batch' && args[1] && BATCH_COMMANDS[args[1]]) {
|
|
249
|
-
commandArgs = args.slice(2);
|
|
250
|
-
const modulePath = BATCH_COMMANDS[args[1]];
|
|
251
|
-
const mod = await import(modulePath);
|
|
252
|
-
await mod.run(commandArgs);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Handle "select" — single command
|
|
476
|
+
// "select" — single command
|
|
257
477
|
if (command === 'select') {
|
|
478
|
+
if (argvHasHelp(commandArgs) && printCommandHelp('select')) return;
|
|
258
479
|
const mod = await import('../scripts/select.js');
|
|
259
480
|
await mod.run(commandArgs);
|
|
260
481
|
return;
|
|
261
482
|
}
|
|
262
483
|
|
|
263
|
-
// Handle "selection <subcommand>" two-word form
|
|
264
|
-
if (command === 'selection' && args[1] && SELECTION_COMMANDS[args[1]]) {
|
|
265
|
-
commandArgs = args.slice(2);
|
|
266
|
-
const modulePath = SELECTION_COMMANDS[args[1]];
|
|
267
|
-
const mod = await import(modulePath);
|
|
268
|
-
await mod.run(commandArgs);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
484
|
const modulePath = COMMANDS[command];
|
|
273
485
|
if (!modulePath) {
|
|
274
|
-
|
|
486
|
+
const suggestion = findClosest(command, allKnownCommands());
|
|
487
|
+
const hint = suggestion
|
|
488
|
+
? `Did you mean "${suggestion}"? (Run "sdlab --help" for all commands.)`
|
|
489
|
+
: 'Run "sdlab --help" for available commands.';
|
|
490
|
+
throw inputError('INPUT_UNKNOWN_COMMAND', `Unknown command: ${command}`, hint);
|
|
275
491
|
}
|
|
276
492
|
|
|
493
|
+
// Per-command --help for one-word commands
|
|
494
|
+
if (argvHasHelp(commandArgs) && printCommandHelp(command)) return;
|
|
495
|
+
|
|
277
496
|
const mod = await import(modulePath);
|
|
278
497
|
await mod.run(commandArgs);
|
|
279
498
|
}
|
|
@@ -68,8 +68,13 @@ export async function buildPackage({ packageDir, partitions, profile, manifest,
|
|
|
68
68
|
const sourcePath = join(projectRoot, record.asset_path);
|
|
69
69
|
if (!existsSync(sourcePath)) continue;
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// D-009: Name output files by record.id (not source basename) to
|
|
72
|
+
// avoid silent collisions when two records share a filename stem.
|
|
73
|
+
const sourceBasename = record.asset_path.split(/[\\/]/).pop();
|
|
74
|
+
const extMatch = sourceBasename.match(/\.[^.]+$/);
|
|
75
|
+
const ext = extMatch ? extMatch[0] : '';
|
|
76
|
+
const filename = `${record.id}${ext}`;
|
|
77
|
+
const stem = record.id;
|
|
73
78
|
const destPath = join(datasetDir, filename);
|
|
74
79
|
|
|
75
80
|
try {
|
|
@@ -57,7 +57,12 @@ export async function buildPackage({ packageDir, partitions, profile, manifest,
|
|
|
57
57
|
const sourcePath = join(projectRoot, record.asset_path);
|
|
58
58
|
if (!existsSync(sourcePath)) continue;
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// D-009: Name output files by record.id (not source basename) to
|
|
61
|
+
// avoid silent collisions when two records share a filename stem.
|
|
62
|
+
const sourceBasename = record.asset_path.split(/[\\/]/).pop();
|
|
63
|
+
const extMatch = sourceBasename.match(/\.[^.]+$/);
|
|
64
|
+
const ext = extMatch ? extMatch[0] : '';
|
|
65
|
+
const filename = `${record.id}${ext}`;
|
|
61
66
|
const destPath = join(datasetDir, filename);
|
|
62
67
|
|
|
63
68
|
try {
|