@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/scripts/curate.js
CHANGED
|
@@ -7,59 +7,73 @@
|
|
|
7
7
|
* node scripts/curate.js <asset_id> approved "Clean silhouette, correct palette" --scores silhouette:0.9,palette:0.8
|
|
8
8
|
* node scripts/curate.js <asset_id> rejected "3D render look, alpha halo" --failures alpha_halo,3d_render_look
|
|
9
9
|
* node scripts/curate.js --list (show uncurated candidates)
|
|
10
|
-
* sdlab curate <asset_id> approved "explanation" --project star-freight
|
|
10
|
+
* sdlab curate <asset_id> approved "explanation" --project star-freight [--dry-run]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { writeFile, rename, readdir, mkdir, readFile } from "node:fs/promises";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { getProjectName } from "../lib/args.js";
|
|
15
|
+
import { parseArgs, getProjectName } from "../lib/args.js";
|
|
16
16
|
import { REPO_ROOT } from "../lib/paths.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const a of args) {
|
|
21
|
-
if (a.startsWith(prefix)) return a.slice(prefix.length);
|
|
22
|
-
}
|
|
23
|
-
const idx = args.indexOf(`--${flag}`);
|
|
24
|
-
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
17
|
+
import { readJsonFile } from "../lib/config.js";
|
|
18
|
+
import { inputError, runtimeError, handleCliError } from "../lib/errors.js";
|
|
19
|
+
import { result } from "../lib/log.js";
|
|
27
20
|
|
|
28
21
|
export async function run(argv = process.argv.slice(2)) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
23
|
+
flags: {
|
|
24
|
+
project: { type: 'string' },
|
|
25
|
+
scores: { type: 'string' },
|
|
26
|
+
failures: { type: 'string' },
|
|
27
|
+
notes: { type: 'string' },
|
|
28
|
+
list: { type: 'boolean', default: false },
|
|
29
|
+
'dry-run': { type: 'boolean', default: false },
|
|
30
|
+
},
|
|
31
|
+
deprecated: { game: 'project' },
|
|
37
32
|
});
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
const projectName = flags.project || getProjectName(argv);
|
|
35
|
+
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
36
|
+
const dryRun = flags['dry-run'] || flags.dryRun;
|
|
37
|
+
|
|
38
|
+
if (flags.list) {
|
|
40
39
|
return listUncurated(GAME_ROOT);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const [assetId, status, explanation
|
|
42
|
+
const [assetId, status, explanation] = positionals;
|
|
44
43
|
|
|
45
44
|
if (!assetId || !status || !explanation) {
|
|
46
|
-
throw
|
|
45
|
+
throw inputError(
|
|
46
|
+
'INPUT_MISSING_ARGS',
|
|
47
|
+
'Usage: sdlab curate <asset_id> <approved|rejected|borderline> <explanation> [--scores k:v,...] [--failures f1,f2] [--dry-run]'
|
|
48
|
+
);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (!["approved", "rejected", "borderline"].includes(status)) {
|
|
50
|
-
throw
|
|
52
|
+
throw inputError(
|
|
53
|
+
'INPUT_BAD_STATUS',
|
|
54
|
+
`Invalid status: ${status}. Must be approved, rejected, or borderline.`
|
|
55
|
+
);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const notesStr = getFlagValue(rest, "notes");
|
|
58
|
+
const scoresStr = flags.scores;
|
|
59
|
+
const failuresStr = flags.failures;
|
|
60
|
+
const notesStr = flags.notes;
|
|
57
61
|
|
|
58
62
|
const criteria_scores = {};
|
|
59
63
|
if (scoresStr) {
|
|
60
64
|
for (const pair of scoresStr.split(",")) {
|
|
61
65
|
const [k, v] = pair.split(":");
|
|
62
|
-
if (k && v)
|
|
66
|
+
if (k && v) {
|
|
67
|
+
const score = parseFloat(v);
|
|
68
|
+
if (!Number.isFinite(score)) {
|
|
69
|
+
throw inputError(
|
|
70
|
+
'INPUT_BAD_SCORE',
|
|
71
|
+
`Score value for "${k.trim()}" is not a number: "${v}"`,
|
|
72
|
+
'Use the form --scores silhouette:0.9,palette:0.8'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
criteria_scores[k.trim()] = score;
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -69,17 +83,44 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
69
83
|
const recordPath = join(GAME_ROOT, `records/${assetId}.json`);
|
|
70
84
|
let record;
|
|
71
85
|
try {
|
|
72
|
-
record =
|
|
73
|
-
} catch {
|
|
74
|
-
|
|
86
|
+
record = await readJsonFile(recordPath);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err && err.code === 'INPUT_FILE_NOT_FOUND') {
|
|
89
|
+
throw inputError('INPUT_UNKNOWN_RECORD', `Record not found: ${recordPath}`);
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
const oldPath = join(GAME_ROOT,
|
|
94
|
+
const originalAssetPath = record.asset_path;
|
|
95
|
+
const oldPath = join(GAME_ROOT, originalAssetPath);
|
|
79
96
|
const newDir = `outputs/${status}`;
|
|
80
97
|
const newPath = `${newDir}/${assetId}.png`;
|
|
98
|
+
const newFullPath = join(GAME_ROOT, newPath);
|
|
99
|
+
|
|
100
|
+
if (dryRun) {
|
|
101
|
+
console.log(`\x1b[33m(dry-run)\x1b[0m ${assetId}`);
|
|
102
|
+
console.log(` status: ${status}`);
|
|
103
|
+
console.log(` move: ${originalAssetPath} -> ${newPath}`);
|
|
104
|
+
console.log(` note: ${explanation}`);
|
|
105
|
+
if (failure_modes.length) console.log(` failures: ${failure_modes.join(", ")}`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
81
109
|
await mkdir(join(GAME_ROOT, newDir), { recursive: true });
|
|
82
110
|
|
|
111
|
+
// Move image FIRST — if the rename fails we still have a clean record.
|
|
112
|
+
try {
|
|
113
|
+
await rename(oldPath, newFullPath);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
throw runtimeError(
|
|
116
|
+
'RUNTIME_MOVE_FAILED',
|
|
117
|
+
`Could not move ${oldPath} to ${newPath}: ${err.message}`,
|
|
118
|
+
'Check that the source image exists and the destination is writable.',
|
|
119
|
+
err
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Now safely update the record. If writing fails, try to restore the image.
|
|
83
124
|
record.asset_path = newPath;
|
|
84
125
|
record.judgment = {
|
|
85
126
|
status,
|
|
@@ -92,16 +133,23 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
92
133
|
confidence: 0.9,
|
|
93
134
|
};
|
|
94
135
|
|
|
95
|
-
await writeFile(recordPath, JSON.stringify(record, null, 2));
|
|
96
|
-
|
|
97
|
-
// Move image file
|
|
98
136
|
try {
|
|
99
|
-
await
|
|
100
|
-
} catch {
|
|
101
|
-
|
|
137
|
+
await writeFile(recordPath, JSON.stringify(record, null, 2));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// Attempt rollback: put the image back.
|
|
140
|
+
try { await rename(newFullPath, oldPath); } catch {}
|
|
141
|
+
throw runtimeError(
|
|
142
|
+
'RUNTIME_WRITE_FAILED',
|
|
143
|
+
`Could not write record ${recordPath}: ${err.message}`,
|
|
144
|
+
null,
|
|
145
|
+
err
|
|
146
|
+
);
|
|
102
147
|
}
|
|
103
148
|
|
|
104
|
-
|
|
149
|
+
// Final artifact path — always shown, even under --quiet.
|
|
150
|
+
result('curate', `${assetId} → ${status}`);
|
|
151
|
+
result(` image: ${newPath}`);
|
|
152
|
+
result(` record: records/${assetId}.json`);
|
|
105
153
|
console.log(` ${explanation}`);
|
|
106
154
|
if (failure_modes.length) console.log(` Failures: ${failure_modes.join(", ")}`);
|
|
107
155
|
}
|
|
@@ -135,8 +183,5 @@ async function listUncurated(GAME_ROOT) {
|
|
|
135
183
|
|
|
136
184
|
// Direct execution guard
|
|
137
185
|
if (process.argv[1] && (process.argv[1].endsWith('curate.js') || process.argv[1].endsWith('curate'))) {
|
|
138
|
-
run().catch(
|
|
139
|
-
console.error(err.message || err);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
});
|
|
186
|
+
run().catch(handleCliError);
|
|
142
187
|
}
|
package/scripts/doctor.js
CHANGED
|
@@ -15,6 +15,7 @@ import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { getProjectName } from '../lib/args.js';
|
|
17
17
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
18
|
+
import { runtimeError, handleCliError } from '../lib/errors.js';
|
|
18
19
|
|
|
19
20
|
const REQUIRED_CONFIG_FILES = [
|
|
20
21
|
'project.json',
|
|
@@ -484,14 +485,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
484
485
|
console.log(`═══ ${status} — ${passes} passed, ${failures} failed, ${warnings} warnings ═══`);
|
|
485
486
|
|
|
486
487
|
if (failures > 0) {
|
|
487
|
-
throw
|
|
488
|
+
throw runtimeError('PROJECT_UNHEALTHY', `Project "${projectName}" has ${failures} issue(s).`);
|
|
488
489
|
}
|
|
489
490
|
}
|
|
490
491
|
|
|
491
492
|
// Direct execution guard
|
|
492
493
|
if (process.argv[1] && (process.argv[1].endsWith('doctor.js') || process.argv[1].endsWith('doctor'))) {
|
|
493
|
-
run().catch(
|
|
494
|
-
console.error(err.message || err);
|
|
495
|
-
process.exit(1);
|
|
496
|
-
});
|
|
494
|
+
run().catch(handleCliError);
|
|
497
495
|
}
|
package/scripts/eligibility.js
CHANGED
|
@@ -4,36 +4,44 @@
|
|
|
4
4
|
* eligibility.js — Audit training/eval eligibility for all project records.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* sdlab eligibility audit [--project <name>]
|
|
7
|
+
* sdlab eligibility audit [--project <name>] [--profile <id>]
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readdir, readFile } from 'node:fs/promises';
|
|
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
14
|
import { loadProjectConfig, loadSelectionProfile } from '../lib/config.js';
|
|
15
|
+
import { handleCliError } from '../lib/errors.js';
|
|
15
16
|
import { evaluateEligibility, categorizeExclusions } from '../lib/eligibility.js';
|
|
16
17
|
|
|
17
18
|
export async function run(argv = process.argv.slice(2)) {
|
|
18
|
-
const
|
|
19
|
+
const { flags } = parseArgs(argv, {
|
|
20
|
+
flags: {
|
|
21
|
+
project: { type: 'string' },
|
|
22
|
+
profile: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
deprecated: { game: 'project' },
|
|
25
|
+
allowUnknown: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const projectName = flags.project || getProjectName(argv);
|
|
19
29
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
20
30
|
|
|
21
|
-
const
|
|
22
|
-
const profileId = profileIdx >= 0 ? argv[profileIdx + 1] : null;
|
|
23
|
-
const profile = loadSelectionProfile(projectRoot, profileId);
|
|
31
|
+
const profile = loadSelectionProfile(projectRoot, flags.profile);
|
|
24
32
|
const config = loadProjectConfig(projectRoot);
|
|
25
33
|
|
|
26
34
|
const recordsDir = join(projectRoot, 'records');
|
|
27
35
|
const files = (await readdir(recordsDir)).filter(f => f.endsWith('.json')).sort();
|
|
28
36
|
|
|
29
37
|
console.log(`\x1b[1msdlab eligibility audit\x1b[0m — ${projectName}`);
|
|
30
|
-
console.log(` Profile: ${
|
|
38
|
+
console.log(` Profile: ${flags.profile || 'training-default (built-in)'}`);
|
|
31
39
|
console.log(` Records: ${files.length}`);
|
|
32
40
|
console.log('');
|
|
33
41
|
|
|
34
42
|
const eligible = [];
|
|
35
43
|
const excluded = [];
|
|
36
|
-
const nearMiss = [];
|
|
44
|
+
const nearMiss = [];
|
|
37
45
|
|
|
38
46
|
for (const file of files) {
|
|
39
47
|
const raw = await readFile(join(recordsDir, file), 'utf-8');
|
|
@@ -55,7 +63,6 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
55
63
|
console.log(` \x1b[31m✗\x1b[0m Excluded: ${excluded.length}`);
|
|
56
64
|
console.log('');
|
|
57
65
|
|
|
58
|
-
// Categorize exclusions
|
|
59
66
|
const categories = categorizeExclusions(excluded);
|
|
60
67
|
console.log(' Exclusion breakdown:');
|
|
61
68
|
for (const [cat, count] of Object.entries(categories)) {
|
|
@@ -65,7 +72,6 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
// Near-miss improvement opportunities
|
|
69
75
|
if (nearMiss.length > 0) {
|
|
70
76
|
console.log(`\n Improvement opportunities (${nearMiss.length} records with 1 failing check):`);
|
|
71
77
|
const byReason = {};
|
|
@@ -84,8 +90,5 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
84
90
|
|
|
85
91
|
// Direct execution guard
|
|
86
92
|
if (process.argv[1] && (process.argv[1].endsWith('eligibility.js') || process.argv[1].endsWith('eligibility'))) {
|
|
87
|
-
run().catch(
|
|
88
|
-
console.error(err.message || err);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
|
93
|
+
run().catch(handleCliError);
|
|
91
94
|
}
|
package/scripts/eval-pack.js
CHANGED
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
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 { buildEvalPack, listEvalPacks, loadEvalPack } from '../lib/eval-pack.js';
|
|
17
18
|
|
|
18
19
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
-
const
|
|
20
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
21
|
+
flags: { project: { type: 'string' } },
|
|
22
|
+
deprecated: { game: 'project' },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const projectName = flags.project || getProjectName(argv);
|
|
20
26
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
21
|
-
const subcommand =
|
|
22
|
-
const args = argv.filter(a => a !== subcommand);
|
|
27
|
+
const subcommand = positionals[0] || 'list';
|
|
23
28
|
|
|
24
29
|
if (subcommand === 'build') {
|
|
25
30
|
console.log(`\x1b[1msdlab eval-pack build\x1b[0m — ${projectName}\n`);
|
|
@@ -48,8 +53,8 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
} else if (subcommand === 'show') {
|
|
51
|
-
const evalId =
|
|
52
|
-
if (!evalId) throw
|
|
56
|
+
const evalId = positionals.find(a => a.startsWith('eval-'));
|
|
57
|
+
if (!evalId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-pack show <eval-id>');
|
|
53
58
|
|
|
54
59
|
const manifest = await loadEvalPack(projectRoot, evalId);
|
|
55
60
|
console.log(`\x1b[1mEval pack\x1b[0m — ${evalId}\n`);
|
|
@@ -80,13 +85,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
} else {
|
|
83
|
-
throw
|
|
88
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, list, show`);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
if (process.argv[1] && (process.argv[1].endsWith('eval-pack.js') || process.argv[1].endsWith('eval-pack'))) {
|
|
88
|
-
run().catch(
|
|
89
|
-
console.error(err.message || err);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
});
|
|
93
|
+
run().catch(handleCliError);
|
|
92
94
|
}
|
package/scripts/eval-run.js
CHANGED
|
@@ -10,40 +10,42 @@
|
|
|
10
10
|
* sdlab eval-run list [--project <name>]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { readFile } from 'node:fs/promises';
|
|
14
13
|
import { join } from 'node:path';
|
|
15
|
-
import { getProjectName } from '../lib/args.js';
|
|
16
|
-
import { REPO_ROOT } from '../lib/paths.js';
|
|
14
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
15
|
+
import { REPO_ROOT, resolveSafeProjectPath } from '../lib/paths.js';
|
|
16
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
17
17
|
import { listTrainingManifests } from '../lib/training-manifests.js';
|
|
18
18
|
import { listEvalPacks } from '../lib/eval-pack.js';
|
|
19
19
|
import { createEvalRun, scoreEvalRun, loadEvalRun, listEvalRuns } from '../lib/eval-runs.js';
|
|
20
20
|
|
|
21
21
|
export async function run(argv = process.argv.slice(2)) {
|
|
22
|
-
const
|
|
22
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
23
|
+
flags: {
|
|
24
|
+
project: { type: 'string' },
|
|
25
|
+
manifest: { type: 'string' },
|
|
26
|
+
'eval-pack': { type: 'string' },
|
|
27
|
+
outputs: { type: 'string' },
|
|
28
|
+
},
|
|
29
|
+
deprecated: { game: 'project' },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const projectName = flags.project || getProjectName(argv);
|
|
23
33
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
24
|
-
|
|
25
|
-
const
|
|
34
|
+
// First non-flag positional that isn't an er-id is the subcommand
|
|
35
|
+
const subcommand = positionals.find(a => !a.startsWith('er-')) || 'list';
|
|
26
36
|
|
|
27
37
|
if (subcommand === 'create') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const manifestIdx = args.indexOf('--manifest');
|
|
31
|
-
if (manifestIdx >= 0) {
|
|
32
|
-
manifestId = args[manifestIdx + 1];
|
|
33
|
-
} else {
|
|
38
|
+
let manifestId = flags.manifest;
|
|
39
|
+
if (!manifestId) {
|
|
34
40
|
const manifests = await listTrainingManifests(projectRoot);
|
|
35
|
-
if (manifests.length === 0) throw
|
|
41
|
+
if (manifests.length === 0) throw inputError('INPUT_NO_MANIFEST', 'No training manifests. Run: sdlab training-manifest create');
|
|
36
42
|
manifestId = manifests[manifests.length - 1].id;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const evalIdx = args.indexOf('--eval-pack');
|
|
42
|
-
if (evalIdx >= 0) {
|
|
43
|
-
evalPackId = args[evalIdx + 1];
|
|
44
|
-
} else {
|
|
45
|
+
let evalPackId = flags['eval-pack'] || flags.evalPack;
|
|
46
|
+
if (!evalPackId) {
|
|
45
47
|
const packs = await listEvalPacks(projectRoot);
|
|
46
|
-
if (packs.length === 0) throw
|
|
48
|
+
if (packs.length === 0) throw inputError('INPUT_NO_EVALPACK', 'No eval packs. Run: sdlab eval-pack build');
|
|
47
49
|
evalPackId = packs[packs.length - 1].id;
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -57,12 +59,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
57
59
|
console.log(` Status: created (use "sdlab eval-run score" to score outputs)`);
|
|
58
60
|
|
|
59
61
|
} else if (subcommand === 'score') {
|
|
60
|
-
const evalRunId =
|
|
61
|
-
if (!evalRunId) throw
|
|
62
|
+
const evalRunId = positionals.find(a => a.startsWith('er-'));
|
|
63
|
+
if (!evalRunId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-run score <eval-run-id> --outputs <path>');
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const outputsPath = args[outputsIdx + 1];
|
|
65
|
+
if (!flags.outputs) throw inputError('INPUT_MISSING_FLAG', '--outputs <path> is required (JSONL with record_id, accept, notes)');
|
|
66
|
+
const outputsPath = resolveSafeProjectPath(projectRoot, flags.outputs, { baseRoot: REPO_ROOT, flagName: 'outputs' });
|
|
66
67
|
|
|
67
68
|
console.log(`\x1b[1msdlab eval-run score\x1b[0m — ${evalRunId}\n`);
|
|
68
69
|
|
|
@@ -80,8 +81,8 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
} else if (subcommand === 'show') {
|
|
83
|
-
const evalRunId =
|
|
84
|
-
if (!evalRunId) throw
|
|
84
|
+
const evalRunId = positionals.find(a => a.startsWith('er-'));
|
|
85
|
+
if (!evalRunId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-run show <eval-run-id>');
|
|
85
86
|
|
|
86
87
|
const run = await loadEvalRun(projectRoot, evalRunId);
|
|
87
88
|
console.log(`\x1b[1mEval run\x1b[0m — ${evalRunId}\n`);
|
|
@@ -112,10 +113,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
} else {
|
|
115
|
-
throw
|
|
116
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: create, score, show, list`);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
if (process.argv[1] && (process.argv[1].endsWith('eval-run.js') || process.argv[1].endsWith('eval-run'))) {
|
|
120
|
-
run().catch(
|
|
121
|
+
run().catch(handleCliError);
|
|
121
122
|
}
|
package/scripts/export.js
CHANGED
|
@@ -9,53 +9,52 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
-
import { getProjectName } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
14
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
14
15
|
import { listSnapshots } from '../lib/snapshot.js';
|
|
15
16
|
import { listSplits } from '../lib/split.js';
|
|
16
|
-
import { buildExport, listExports
|
|
17
|
+
import { buildExport, listExports } from '../lib/export.js';
|
|
17
18
|
|
|
18
19
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
-
const
|
|
20
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
21
|
+
flags: {
|
|
22
|
+
project: { type: 'string' },
|
|
23
|
+
snapshot: { type: 'string' },
|
|
24
|
+
split: { type: 'string' },
|
|
25
|
+
profile: { type: 'string' },
|
|
26
|
+
copy: { type: 'boolean', default: false },
|
|
27
|
+
},
|
|
28
|
+
deprecated: { game: 'project' },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const projectName = flags.project || getProjectName(argv);
|
|
20
32
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
21
|
-
const subcommand =
|
|
22
|
-
const args = argv.filter(a => a !== subcommand);
|
|
33
|
+
const subcommand = positionals[0] || 'list';
|
|
23
34
|
|
|
24
35
|
if (subcommand === 'build') {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const snapIdx = args.indexOf('--snapshot');
|
|
28
|
-
if (snapIdx >= 0) {
|
|
29
|
-
snapshotId = args[snapIdx + 1];
|
|
30
|
-
} else {
|
|
36
|
+
let snapshotId = flags.snapshot;
|
|
37
|
+
if (!snapshotId) {
|
|
31
38
|
const snapshots = await listSnapshots(projectRoot);
|
|
32
|
-
if (snapshots.length === 0) throw
|
|
39
|
+
if (snapshots.length === 0) throw inputError('INPUT_NO_SNAPSHOT', 'No snapshots found. Run: sdlab snapshot create');
|
|
33
40
|
snapshotId = snapshots[snapshots.length - 1].id;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const splitIdx = args.indexOf('--split');
|
|
39
|
-
if (splitIdx >= 0) {
|
|
40
|
-
splitId = args[splitIdx + 1];
|
|
41
|
-
} else {
|
|
43
|
+
let splitId = flags.split;
|
|
44
|
+
if (!splitId) {
|
|
42
45
|
const splits = await listSplits(projectRoot);
|
|
43
|
-
if (splits.length === 0) throw
|
|
46
|
+
if (splits.length === 0) throw inputError('INPUT_NO_SPLIT', 'No splits found. Run: sdlab split build');
|
|
44
47
|
splitId = splits[splits.length - 1].id;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
const profileIdx = args.indexOf('--profile');
|
|
48
|
-
const profileId = profileIdx >= 0 ? args[profileIdx + 1] : null;
|
|
49
|
-
const copy = args.includes('--copy');
|
|
50
|
-
|
|
51
50
|
console.log(`\x1b[1msdlab export build\x1b[0m — ${projectName}`);
|
|
52
51
|
console.log(` Snapshot: ${snapshotId}`);
|
|
53
52
|
console.log(` Split: ${splitId}`);
|
|
54
|
-
console.log(` Profile: ${
|
|
55
|
-
console.log(` Images: ${copy ? 'copy' : 'symlink'}`);
|
|
53
|
+
console.log(` Profile: ${flags.profile || 'canonical-package (built-in)'}`);
|
|
54
|
+
console.log(` Images: ${flags.copy ? 'copy' : 'symlink'}`);
|
|
56
55
|
console.log('');
|
|
57
56
|
|
|
58
|
-
const result = await buildExport(projectRoot, snapshotId, splitId, { profileId, copy });
|
|
57
|
+
const result = await buildExport(projectRoot, snapshotId, splitId, { profileId: flags.profile, copy: flags.copy });
|
|
59
58
|
|
|
60
59
|
console.log(` \x1b[32m✓\x1b[0m Export: ${result.exportId}`);
|
|
61
60
|
console.log(` Records: ${result.recordCount}`);
|
|
@@ -73,13 +72,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
} else {
|
|
76
|
-
throw
|
|
75
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, list`);
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
if (process.argv[1] && (process.argv[1].endsWith('export.js') || process.argv[1].endsWith('export'))) {
|
|
81
|
-
run().catch(
|
|
82
|
-
console.error(err.message || err);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
});
|
|
80
|
+
run().catch(handleCliError);
|
|
85
81
|
}
|