orizu 0.3.3 → 0.3.5

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/README.md CHANGED
@@ -169,10 +169,29 @@ orizu logout
169
169
  --output-schema ./schemas/support-output.json</code></pre>
170
170
  </td>
171
171
  </tr>
172
+ <tr>
173
+ <td>Preview an app locally</td>
174
+ <td>
175
+ <pre><code>orizu apps preview \
176
+ --file ./apps/SupportLabeler.tsx \
177
+ --input-schema ./schemas/support-input.json \
178
+ --output-schema ./schemas/support-output.json \
179
+ --sample-row ./fixtures/sample-row.json \
180
+ --screenshot ./preview.png</code></pre>
181
+ </td>
182
+ </tr>
183
+ <tr>
184
+ <td>Preview runtime</td>
185
+ <td>The CLI uses the live Orizu web checkout when it is nearby, otherwise it uses the bundled preview runtime snapshot shipped in this package.</td>
186
+ </tr>
172
187
  <tr>
173
188
  <td>Link a dataset to an app</td>
174
189
  <td><code>orizu apps link-dataset --app &lt;appId&gt; --dataset &lt;datasetId&gt;</code></td>
175
190
  </tr>
191
+ <tr>
192
+ <td>Export app source</td>
193
+ <td><code>orizu apps export --app &lt;appId&gt; --project ops-eval/support-qa --out ./apps/SupportLabeler.tsx</code></td>
194
+ </tr>
176
195
  <tr>
177
196
  <td colspan="2"><strong>Tasks</strong></td>
178
197
  </tr>
package/dist/index.js CHANGED
@@ -14,8 +14,10 @@ import { parseDatasetFile } from './file-parser.js';
14
14
  import { streamJsonlRowChunks } from './jsonl-stream.js';
15
15
  import { parseDatasetReference } from './dataset-download.js';
16
16
  import { parseGlobalFlags } from './global-flags.js';
17
+ import { runLocalAppPreview } from './preview-runtime.js';
17
18
  import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
18
19
  import { formatTaskCreateError } from './task-create-error.js';
20
+ const MAX_README_LENGTH = 200_000;
19
21
  function getCliVersion() {
20
22
  const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
21
23
  if (typeof packageJson.version !== 'string' || packageJson.version.length === 0) {
@@ -30,10 +32,14 @@ function printVersion() {
30
32
  printLine(`orizu ${getCliVersion()}`);
31
33
  }
32
34
  function printUsage() {
33
- printLine(`orizu global options:\n\n --local Use http://localhost:3000\n --server <url> Use a specific server origin (for example: https://preview.example.com)\n --version, -v Print the orizu CLI version\n\norizu commands:\n\n orizu login [--no-prompt-if-logged-in]\n orizu logout\n orizu whoami\n orizu env [--project <team/project>] [--project-id <projectId>]\n orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json\n orizu teams list\n orizu teams create [--name <name>]\n orizu teams members list [--team <teamSlug>]\n orizu teams members add --email <email> [--team <teamSlug>]\n orizu teams members remove --email <email> [--team <teamSlug>]\n orizu teams members role --team <teamSlug> --email <email> --role <admin|member>\n orizu projects list [--team <teamSlug>]\n orizu projects create --name <name> [--team <teamSlug>]\n orizu prompts list --project <team/project>\n orizu prompts comments <prompt-id-or-name> --project <team/project> [--label <label> | --version <id>] [--json]\n orizu prompts pull <prompt-id-or-name> --project <team/project> --out <dir> [--label <label> | --version <id>] [--json]\n orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]\n orizu prompts labels set <prompt-name> <label> --version <version-id> [--project <team/project>] [--json]\n orizu prompts scorers set-headline <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu prompts scorers add <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu scorers list --project <team/project>\n orizu scorers register --project <team/project> --name <name> --manifest <manifest.json> [--prompt-version <id>] [--runner-version <id>] [--label <label>] [--json]\n orizu scorers detail <scorer-id-or-name> --project <team/project> [--json]\n orizu scorers labels set <scorer-name> <label> --version <scorer-version-id> [--project <team/project>] [--json]\n orizu scores submit <results.jsonl|results.json> --scorer-version <id> --subject-version <prompt-version-id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu judges list --project <team/project>\n orizu judges pull <judge-id-or-name> --project <team/project> --out <dir> [--label <label> | --version <id>] [--json]\n orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]\n orizu runners push <dir> [--project <team/project>] [--name <name>] [--label <label>] [--json]\n orizu runners exec (--prompt <prompt-version-id> | --prompt-version <id> --runner-version <id> | --scorer-version <id>) --dataset-version <id> --split-set <id-or-name> --split <name> [--runner-dir <dir>] --out <results.jsonl|results.jsonl.gz>\n orizu optimizers push <dir> [--project <team/project>] [--name <name>] [--label <label>] [--json]\n orizu runs submit <results.jsonl|results.jsonl.gz> --prompt-version <id> --runner-version <id> --dataset-version <id> --split-set <id> --split <name> [--project <team/project>]\n orizu apps list [--project <team/project>]\n orizu apps create --project <team/project> --name <name> --dataset <datasetId> --file <path> --input-schema <json-path> --output-schema <json-path> [--component <name>]\n orizu apps update [--app <appId>] [--project <team/project>] --file <path> --input-schema <json-path> --output-schema <json-path> [--component <name>]\n orizu apps link-dataset --dataset <datasetId> [--app <appId>] [--project <team/project>] [--version <n>]\n orizu apps detail --app <appId> [--project <team/project>] [--json]\n orizu tasks list [--project <team/project>]\n orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> --assignees <userIdOrEmail1,userIdOrEmail2> [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]\n orizu tasks assign --task <taskId> --assignees <userId1,userId2>\n orizu tasks status --task <taskId> [--json]\n orizu tasks pause --task <taskId>\n orizu tasks unpause --task <taskId>\n orizu datasets upload --file <path> [--project <team/project>] [--name <name>]\n orizu datasets push <path> [--project <team/project>] [--name <name>] [--json]\n orizu datasets versions create <datasetId|dataset-name> [--project <team/project>] [--label <label>] [--json]\n orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--json]\n orizu datasets download [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--format <csv|json|jsonl>] [--out <path>]\n orizu datasets append [--dataset <datasetId|datasetUrl>] [--project <team/project>] --file <path>\n orizu datasets edit-rows [--dataset <datasetId|datasetUrl>] [--project <team/project>] --file <path>\n orizu datasets delete-rows [--dataset <datasetId|datasetUrl>] [--project <team/project>] --row-ids <id1,id2>\n orizu datasets delete [--dataset <datasetId|datasetUrl>] [--project <team/project>]\n orizu datasets lock [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--reason <text>]\n orizu datasets clone [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--name <name>]\n orizu tasks export [--task <taskId>] [--format <csv|json|jsonl>] [--out <path>]`);
35
+ printLine(`orizu global options:\n\n --local Use http://localhost:3000\n --server <url> Use a specific server origin (for example: https://preview.example.com)\n --version, -v Print the orizu CLI version\n\norizu commands:\n\n orizu login [--no-prompt-if-logged-in]\n orizu logout\n orizu whoami\n orizu env [--project <team/project>] [--project-id <projectId>]\n orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json\n orizu teams list\n orizu teams create [--name <name>]\n orizu teams members list [--team <teamSlug>]\n orizu teams members add --email <email> [--team <teamSlug>]\n orizu teams members remove --email <email> [--team <teamSlug>]\n orizu teams members role --team <teamSlug> --email <email> --role <admin|member>\n orizu projects list [--team <teamSlug>]\n orizu projects create --name <name> [--team <teamSlug>]\n orizu prompts list --project <team/project>\n orizu prompts comments <prompt-id-or-name> --project <team/project> [--label <label> | --version <id>] [--json]\n orizu prompts pull <prompt-id-or-name> --project <team/project> --out <dir> [--label <label> | --version <id>] [--json]\n orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]\n orizu prompts labels set <prompt-name> <label> --version <version-id> [--project <team/project>] [--json]\n orizu prompts scorers set-headline <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu prompts scorers add <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu scorers list --project <team/project>\n orizu scorers register --project <team/project> --name <name> --manifest <manifest.json> [--prompt-version <id>] [--runner-version <id>] [--label <label>] [--json]\n orizu scorers detail <scorer-id-or-name> --project <team/project> [--json]\n orizu scorers labels set <scorer-name> <label> --version <scorer-version-id> [--project <team/project>] [--json]\n orizu scorers exec --scorer-version <id> (--subject-version <prompt-version-id> | --optimization-run <id> --candidate <id>) --dataset-version <id> --split-set <id> --split <name> [--subject-results <jsonl>] [--dependency-score-run <alias=id>] [--dependency-results <alias=path>] [--no-submit] [--out <score.json>] [--project <team/project>] [--json]\n orizu scores submit <results.jsonl|results.json> --scorer-version <id> (--subject-version <prompt-version-id> | --optimization-run <id> --candidate <id>) [--aggregate] [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]\n orizu judges list --project <team/project>\n orizu judges pull <judge-id-or-name> --project <team/project> --out <dir> [--label <label> | --version <id>] [--json]\n orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]\n orizu runners push <dir> [--project <team/project>] [--name <name>] [--label <label>] [--json]\n orizu runners exec (--prompt <prompt-version-id> | --prompt-version <id> --runner-version <id> | --scorer-version <id>) --dataset-version <id> --split-set <id-or-name> --split <name> [--runner-dir <dir>] --out <results.jsonl|results.jsonl.gz>\n orizu optimizers push <dir> [--project <team/project>] [--name <name>] [--label <label>] [--json]\n orizu runs submit <results.jsonl|results.jsonl.gz> --prompt-version <id> --runner-version <id> --dataset-version <id> --split-set <id> --split <name> [--project <team/project>]\n orizu apps list [--project <team/project>]\n orizu apps create --project <team/project> --name <name> --dataset <datasetId> --file <path> --input-schema <json-path> --output-schema <json-path> [--component <name>]\n orizu apps update [--app <appId>] [--project <team/project>] --file <path> --input-schema <json-path> --output-schema <json-path> [--component <name>]\n orizu apps link-dataset --dataset <datasetId> [--app <appId>] [--project <team/project>] [--version <n>]\n orizu apps detail --app <appId> [--project <team/project>] [--json]\n orizu apps export [--app <appId>] [--project <team/project>] [--version <n>] [--out <path>]\n orizu tasks list [--project <team/project>]\n orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> --assignees <userIdOrEmail1,userIdOrEmail2> [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]\n orizu tasks assign --task <taskId> --assignees <userId1,userId2>\n orizu tasks status --task <taskId> [--json]\n orizu tasks pause --task <taskId>\n orizu tasks unpause --task <taskId>\n orizu datasets upload --file <path> [--project <team/project>] [--name <name>] [--readme-file <README.md> | --readme-text <markdown>]\n orizu datasets push <path> [--project <team/project>] [--name <name>] [--readme-file <README.md> | --readme-text <markdown>] [--json]\n orizu datasets readme set <datasetId|dataset-name> [--project <team/project>] (--readme-file <README.md> | --readme-text <markdown>) [--json]\n orizu datasets versions create <datasetId|dataset-name> [--project <team/project>] [--label <label>] [--readme-file <README.md> | --readme-text <markdown>] [--json]\n orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--json]\n orizu datasets download [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--format <csv|json|jsonl>] [--out <path>]\n orizu datasets append [--dataset <datasetId|datasetUrl>] [--project <team/project>] --file <path>\n orizu datasets edit-rows [--dataset <datasetId|datasetUrl>] [--project <team/project>] --file <path>\n orizu datasets delete-rows [--dataset <datasetId|datasetUrl>] [--project <team/project>] --row-ids <id1,id2>\n orizu datasets delete [--dataset <datasetId|datasetUrl>] [--project <team/project>]\n orizu datasets lock [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--reason <text>]\n orizu datasets clone [--dataset <datasetId|datasetUrl>] [--project <team/project>] [--name <name>]\n orizu tasks export [--task <taskId>] [--format <csv|json|jsonl>] [--out <path>]`);
36
+ printPreviewUsage();
37
+ }
38
+ function printPreviewUsage() {
39
+ printLine(`\nLocal app preview:\n\n orizu apps preview --file <path> --input-schema <json-path> --output-schema <json-path> --sample-row <json-path> [--screenshot <png-path>] [--headed] [--keep-open] [--component <name>]`);
34
40
  }
35
41
  function printOptimizationUsage() {
36
- printLine(`\nOptimization lifecycle commands:\n\n orizu optimizations start --project <team/project> --optimizer-version <id> --prompt-version <id[,id]> --selection-scorer <id> [--reflection-scorer <id>] [--pareto-scorer <id>] [--best-scorer <id>] --dataset-version <id> --split-set <id> [--train-split <name>] [--validation-split <name>] [--metadata <json|@file>] [--json]\n orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> --candidate-version-id <id> --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--log-dir logs]\n orizu optimizations export <run-id> [--out <path>] [--json]\n orizu optimizations pause <run-id> [--reason <text>] [--json]\n orizu optimizations resume <run-id> [--json]\n orizu optimizations finish <run-id> [--best-score <n>] [--best-candidate <id>] [--result-prompt-version <id>] [--metadata <json|@file>] [--json]\n orizu optimizations fail <run-id> [--reason <text>] [--metadata <json|@file>] [--json]\n orizu optimizations cancel <run-id> [--reason <text>] [--json]`);
42
+ printLine(`\nOptimization lifecycle commands:\n\n orizu optimizations start --project <team/project> --optimizer-version <id> --prompt-version <id[,id]> --selection-scorer <id> [--reflection-scorer <id>] [--pareto-scorer <id>] [--best-scorer <id>] --dataset-version <id> --split-set <id> [--train-split <name>] [--validation-split <name>] [--metadata <json|@file>] [--json]\n orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> --candidate-version-id <id> --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--log-dir logs]\n orizu optimizations export <run-id> [--out <path>] [--json]\n orizu optimizations pause <run-id> [--reason <text>] [--json]\n orizu optimizations resume <run-id> [--json]\n orizu optimizations finish <run-id> [--best-score <n>] [--best-candidate <id>] [--result-prompt-version <id>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations fail <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations cancel <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--json]`);
37
43
  }
38
44
  let cliArgs = process.argv.slice(2);
39
45
  function getArg(name) {
@@ -59,6 +65,20 @@ export function expandHomePath(path) {
59
65
  }
60
66
  return path;
61
67
  }
68
+ function readReadmeMarkdownFromArgs(usage) {
69
+ const readmeFile = getArg('--readme-file');
70
+ const readmeText = getArg('--readme-text');
71
+ if (readmeFile !== null && readmeText !== null) {
72
+ throw new Error(`Use either --readme-file or --readme-text, not both.\n${usage}`);
73
+ }
74
+ const markdown = readmeFile !== null
75
+ ? readFileSync(expandHomePath(readmeFile), 'utf8')
76
+ : readmeText;
77
+ if (markdown !== null && markdown.length > MAX_README_LENGTH) {
78
+ throw new Error(`README markdown is too large. Maximum size is ${MAX_README_LENGTH} characters.`);
79
+ }
80
+ return markdown;
81
+ }
62
82
  function createCodeVerifier() {
63
83
  return randomBytes(32).toString('base64url');
64
84
  }
@@ -1244,14 +1264,19 @@ async function createDatasetVersion() {
1244
1264
  const datasetId = getPositionalArg(3) || getArg('--dataset');
1245
1265
  const versionLabel = getArg('--label') || getArg('--version-label') || null;
1246
1266
  const project = getArg('--project');
1267
+ const usage = 'Usage: orizu datasets versions create <datasetId|dataset-name> [--project <team/project>] [--label <label>] [--readme-file <README.md> | --readme-text <markdown>] [--json]';
1247
1268
  if (!datasetId) {
1248
- throw new Error('Usage: orizu datasets versions create <datasetId|dataset-name> [--project <team/project>] [--label <label>] [--json]');
1269
+ throw new Error(usage);
1249
1270
  }
1271
+ const readmeMarkdown = readReadmeMarkdownFromArgs(usage);
1250
1272
  const resolvedDatasetId = await resolveDatasetIdForProject(datasetId, project);
1251
1273
  const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(resolvedDatasetId)}/versions`, {
1252
1274
  method: 'POST',
1253
1275
  headers: { 'Content-Type': 'application/json' },
1254
- body: JSON.stringify({ versionLabel }),
1276
+ body: JSON.stringify({
1277
+ versionLabel,
1278
+ ...(readmeMarkdown !== null ? { readmeMarkdown } : {}),
1279
+ }),
1255
1280
  });
1256
1281
  if (!response.ok) {
1257
1282
  throw new Error(`Failed to create dataset version: ${await response.text()}`);
@@ -1364,17 +1389,22 @@ async function submitScoreResults() {
1364
1389
  const optimizationRunId = getArg('--optimization-run');
1365
1390
  const candidateId = getArg('--candidate');
1366
1391
  if (!resultsPath || !scorerVersionId || (!subjectPromptVersionId && (!optimizationRunId || !candidateId))) {
1367
- throw new Error('Usage: orizu scores submit <results.jsonl|results.json> --project <team/project> --scorer-version <id> (--subject-version <prompt-version-id> | --optimization-run <id> --candidate <id>) [--dataset-version <id> --split-set <id> --split <name>] [--json]');
1392
+ throw new Error('Usage: orizu scores submit <results.jsonl|results.json> --project <team/project> --scorer-version <id> (--subject-version <prompt-version-id> | --optimization-run <id> --candidate <id>) [--aggregate] [--dataset-version <id> --split-set <id> --split <name>] [--json]');
1368
1393
  }
1369
1394
  const resultBytes = readSourceBytes(resultsPath);
1370
1395
  const raw = resultsPath.endsWith('.gz')
1371
1396
  ? gunzipSync(resultBytes).toString('utf8')
1372
1397
  : resultBytes.toString('utf8');
1373
- const resultsJsonl = normalizeScoreResultsInput(resultsPath, raw);
1398
+ const scoreInput = normalizeScoreResultsInput(resultsPath, raw, hasArg('--aggregate'));
1399
+ if (scoreInput.aggregateWarning) {
1400
+ console.warn(scoreInput.aggregateWarning);
1401
+ }
1402
+ const aggregatePayload = scoreInput.aggregate || {};
1374
1403
  const response = await authedFetch(`/api/cli/scores/submit?project=${encodeURIComponent(project)}`, {
1375
1404
  method: 'POST',
1376
1405
  headers: { 'Content-Type': 'application/json' },
1377
1406
  body: JSON.stringify({
1407
+ ...aggregatePayload,
1378
1408
  scorerVersionId,
1379
1409
  subjectPromptVersionId: subjectPromptVersionId || undefined,
1380
1410
  datasetVersionId: getArg('--dataset-version') || undefined,
@@ -1382,7 +1412,7 @@ async function submitScoreResults() {
1382
1412
  splitName: getArg('--split') || undefined,
1383
1413
  optimizationRunId: optimizationRunId || undefined,
1384
1414
  candidateId: candidateId || undefined,
1385
- resultsJsonl,
1415
+ resultsJsonl: scoreInput.resultsJsonl,
1386
1416
  }),
1387
1417
  });
1388
1418
  if (!response.ok) {
@@ -1396,23 +1426,150 @@ async function submitScoreResults() {
1396
1426
  printLine(`Submitted score ${sanitizeTerminalText(data.scoreRun.id)} ` +
1397
1427
  `(${formatPercent(data.scoreRun.scoreValue)})`);
1398
1428
  }
1399
- function normalizeScoreResultsInput(sourcePath, raw) {
1429
+ function looksLikeAggregateScoreObject(value) {
1430
+ return (value.scoreValue !== undefined ||
1431
+ value.score_value !== undefined ||
1432
+ value.diagnostics !== undefined ||
1433
+ value.feedbackSummary !== undefined ||
1434
+ value.feedback_summary !== undefined ||
1435
+ value.rowEvidence !== undefined ||
1436
+ value.row_evidence !== undefined ||
1437
+ value.dependencyScoreRunIds !== undefined ||
1438
+ value.dependency_score_run_ids !== undefined);
1439
+ }
1440
+ function numberFromUnknown(value) {
1441
+ if (typeof value === 'number' && Number.isFinite(value))
1442
+ return value;
1443
+ if (typeof value === 'string' && value.trim()) {
1444
+ const parsed = Number(value);
1445
+ return Number.isFinite(parsed) ? parsed : null;
1446
+ }
1447
+ if (typeof value === 'boolean')
1448
+ return value ? 1 : 0;
1449
+ return null;
1450
+ }
1451
+ function normalizeScoreResultsInput(sourcePath, raw, aggregateMode = false) {
1400
1452
  const logicalPath = sourcePath.endsWith('.gz') ? sourcePath.slice(0, -3) : sourcePath;
1401
1453
  if (!logicalPath.endsWith('.json'))
1402
- return raw;
1454
+ return { resultsJsonl: raw };
1403
1455
  try {
1404
1456
  const parsed = JSON.parse(raw);
1457
+ if (aggregateMode) {
1458
+ if (!isRecord(parsed)) {
1459
+ throw new Error('Aggregate score results JSON must be an object');
1460
+ }
1461
+ return { aggregate: parsed };
1462
+ }
1463
+ if (isRecord(parsed) && looksLikeAggregateScoreObject(parsed)) {
1464
+ return {
1465
+ aggregate: parsed,
1466
+ aggregateWarning: 'Warning: aggregate score JSON detected. Pass --aggregate to make this explicit.',
1467
+ };
1468
+ }
1405
1469
  const rows = Array.isArray(parsed) ? parsed : [parsed];
1406
1470
  if (rows.some(row => !isRecord(row))) {
1407
1471
  throw new Error('JSON score results must be an object or an array of objects');
1408
1472
  }
1409
- return rows.map(row => JSON.stringify(row)).join('\n');
1473
+ return { resultsJsonl: rows.map(row => JSON.stringify(row)).join('\n') };
1410
1474
  }
1411
1475
  catch (error) {
1412
1476
  const message = error instanceof Error ? error.message : String(error);
1413
1477
  throw new Error(`Invalid score results JSON '${sourcePath}': ${message}`);
1414
1478
  }
1415
1479
  }
1480
+ function readOptionalJsonlFile(pathArg) {
1481
+ if (!pathArg)
1482
+ return undefined;
1483
+ const bytes = readSourceBytes(pathArg);
1484
+ return pathArg.endsWith('.gz')
1485
+ ? gunzipSync(bytes).toString('utf8')
1486
+ : bytes.toString('utf8');
1487
+ }
1488
+ function parseAliasAssignments(value) {
1489
+ const result = {};
1490
+ if (!value)
1491
+ return result;
1492
+ for (const item of parseCommaSeparated(value)) {
1493
+ const equalsIndex = item.indexOf('=');
1494
+ if (equalsIndex <= 0 || equalsIndex === item.length - 1) {
1495
+ throw new Error(`Expected alias assignment in form alias=value, got '${item}'`);
1496
+ }
1497
+ result[item.slice(0, equalsIndex)] = item.slice(equalsIndex + 1);
1498
+ }
1499
+ return result;
1500
+ }
1501
+ function readDependencyResultAssignments(value) {
1502
+ const assignments = parseAliasAssignments(value);
1503
+ const result = {};
1504
+ for (const [alias, path] of Object.entries(assignments)) {
1505
+ result[alias] = readOptionalJsonlFile(path) || '';
1506
+ }
1507
+ return Object.keys(result).length > 0 ? result : undefined;
1508
+ }
1509
+ async function execScorer() {
1510
+ const project = getArg('--project') || await resolveProjectSlug(null);
1511
+ const scorerVersionId = getArg('--scorer-version');
1512
+ const subjectPromptVersionId = getArg('--subject-version') || getArg('--prompt-version');
1513
+ const optimizationRunId = getArg('--optimization-run');
1514
+ const candidateId = getArg('--candidate');
1515
+ const datasetVersionId = getArg('--dataset-version');
1516
+ const splitSetId = getArg('--split-set');
1517
+ const splitName = getArg('--split');
1518
+ const outPath = getArg('--out');
1519
+ if (!scorerVersionId ||
1520
+ (!subjectPromptVersionId && (!optimizationRunId || !candidateId)) ||
1521
+ !datasetVersionId ||
1522
+ !splitSetId ||
1523
+ !splitName) {
1524
+ throw new Error('Usage: orizu scorers exec --scorer-version <id> (--subject-version <prompt-version-id> | --optimization-run <id> --candidate <id>) --dataset-version <id> --split-set <id> --split <name> [--subject-results <jsonl>] [--dependency-score-run <alias=id>] [--dependency-results <alias=path>] [--no-submit] [--out <score.json>] [--project <team/project>] [--json]');
1525
+ }
1526
+ const response = await authedFetch(`/api/cli/scorers/exec?project=${encodeURIComponent(project)}`, {
1527
+ method: 'POST',
1528
+ headers: { 'Content-Type': 'application/json' },
1529
+ body: JSON.stringify({
1530
+ scorerVersionId,
1531
+ subjectPromptVersionId: subjectPromptVersionId || undefined,
1532
+ optimizationRunId: optimizationRunId || undefined,
1533
+ candidateId: candidateId || undefined,
1534
+ datasetVersionId,
1535
+ splitSetId,
1536
+ splitName,
1537
+ submit: !hasArg('--no-submit'),
1538
+ reuseExisting: !hasArg('--no-reuse-existing'),
1539
+ subjectResultsJsonl: readOptionalJsonlFile(getArg('--subject-results') || getArg('--outputs')),
1540
+ dependencyScoreRunIds: parseAliasAssignments(getArg('--dependency-score-run') || getArg('--dependency-score-runs')),
1541
+ dependencyResultsJsonl: readDependencyResultAssignments(getArg('--dependency-results')),
1542
+ }),
1543
+ });
1544
+ if (!response.ok) {
1545
+ throw new Error(`Failed to execute scorer: ${await response.text()}`);
1546
+ }
1547
+ const data = await parseJsonResponse(response, 'Scorer exec');
1548
+ if (outPath) {
1549
+ writeFileSync(expandHomePath(outPath), `${JSON.stringify(data.scoreResult || data, null, 2)}\n`);
1550
+ }
1551
+ if (hasJsonFlag()) {
1552
+ printJson(data);
1553
+ return;
1554
+ }
1555
+ const scoreRun = isRecord(data.scoreRun) ? data.scoreRun : null;
1556
+ const scoreResult = isRecord(data.scoreResult) ? data.scoreResult : null;
1557
+ const scoreValue = scoreRun
1558
+ ? scoreRun.scoreValue
1559
+ : scoreResult
1560
+ ? scoreResult.scoreValue
1561
+ : null;
1562
+ if (scoreRun && typeof scoreRun.id === 'string') {
1563
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} -> score ${sanitizeTerminalText(scoreRun.id)} (${formatPercent(numberFromUnknown(scoreValue))})`);
1564
+ }
1565
+ else {
1566
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} (${formatPercent(numberFromUnknown(scoreValue))})`);
1567
+ }
1568
+ if (outPath) {
1569
+ printLine(`Wrote scorer result to ${sanitizeTerminalText(outPath)}`);
1570
+ }
1571
+ }
1572
+ const OPTIMIZATION_REPORT_MAX_BYTES = 2 * 1024 * 1024;
1416
1573
  function parseOptionalNumberFlag(name) {
1417
1574
  const value = getArg(name);
1418
1575
  if (!value) {
@@ -1427,6 +1584,40 @@ function parseOptionalNumberFlag(name) {
1427
1584
  function hasObjectKeys(value) {
1428
1585
  return Object.keys(value).length > 0;
1429
1586
  }
1587
+ function readOptimizationReportInput() {
1588
+ const report = getArg('--report');
1589
+ const reportFile = getArg('--report-file');
1590
+ if (report && reportFile) {
1591
+ throw new Error('Use either --report or --report-file, not both');
1592
+ }
1593
+ if (!report && !reportFile) {
1594
+ return null;
1595
+ }
1596
+ let markdown;
1597
+ let sourceName;
1598
+ if (reportFile) {
1599
+ const expandedPath = expandHomePath(reportFile);
1600
+ markdown = readSourceFile(reportFile);
1601
+ sourceName = basename(expandedPath);
1602
+ }
1603
+ else if (report?.startsWith('@')) {
1604
+ const path = report.slice(1);
1605
+ const expandedPath = expandHomePath(path);
1606
+ markdown = readSourceFile(path);
1607
+ sourceName = basename(expandedPath);
1608
+ }
1609
+ else {
1610
+ markdown = report || '';
1611
+ sourceName = 'inline';
1612
+ }
1613
+ if (!markdown.trim()) {
1614
+ throw new Error('Optimization report markdown must not be blank');
1615
+ }
1616
+ if (Buffer.byteLength(markdown, 'utf8') > OPTIMIZATION_REPORT_MAX_BYTES) {
1617
+ throw new Error(`Optimization report exceeds ${OPTIMIZATION_REPORT_MAX_BYTES} bytes`);
1618
+ }
1619
+ return { markdown, sourceName };
1620
+ }
1430
1621
  async function startOptimizationRun() {
1431
1622
  const project = getArg('--project') || await resolveProjectSlug(null);
1432
1623
  const optimizerVersionId = getArg('--optimizer-version');
@@ -1503,6 +1694,10 @@ async function updateOptimizationRunLifecycle(action) {
1503
1694
  };
1504
1695
  const metadata = readJsonObjectArg(getArg('--metadata'), 'optimization metadata');
1505
1696
  const reason = getArg('--reason');
1697
+ const report = readOptimizationReportInput();
1698
+ if (report && (action === 'pause' || action === 'resume')) {
1699
+ throw new Error(`orizu optimizations ${action} does not accept optimization reports`);
1700
+ }
1506
1701
  if ((action === 'pause' || action === 'cancel') && reason) {
1507
1702
  metadata.reason = reason;
1508
1703
  }
@@ -1513,6 +1708,10 @@ async function updateOptimizationRunLifecycle(action) {
1513
1708
  if (hasObjectKeys(metadata)) {
1514
1709
  body.metadata = metadata;
1515
1710
  }
1711
+ if (report) {
1712
+ body.reportMarkdown = report.markdown;
1713
+ body.reportSourceName = report.sourceName;
1714
+ }
1516
1715
  if (action === 'finish') {
1517
1716
  const bestScore = parseOptionalNumberFlag('--best-score');
1518
1717
  if (bestScore !== undefined) {
@@ -1957,10 +2156,51 @@ async function createAppFromFile() {
1957
2156
  }
1958
2157
  const data = await parseJsonResponse(response, 'App create');
1959
2158
  printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`);
2159
+ if (data.app.url) {
2160
+ printLine(`View app: ${formatTerminalLink(data.app.url)}`);
2161
+ }
1960
2162
  if (data.warnings?.length) {
1961
2163
  printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`);
1962
2164
  }
1963
2165
  }
2166
+ async function previewAppFromFile() {
2167
+ const filePath = getArg('--file');
2168
+ const inputSchemaPath = getArg('--input-schema');
2169
+ const outputSchemaPath = getArg('--output-schema');
2170
+ const sampleRowPath = getArg('--sample-row');
2171
+ const screenshotPath = getArg('--screenshot');
2172
+ const component = getArg('--component') || undefined;
2173
+ const headed = hasArg('--headed');
2174
+ const keepOpen = hasArg('--keep-open');
2175
+ if (!filePath || !inputSchemaPath || !outputSchemaPath || !sampleRowPath) {
2176
+ throw new Error('Usage: orizu apps preview --file <path> --input-schema <json-path> --output-schema <json-path> --sample-row <json-path> [--screenshot <png-path>] [--headed] [--keep-open] [--component <name>]');
2177
+ }
2178
+ const expandedFilePath = realpathSync(expandHomePath(filePath));
2179
+ const inputJsonSchema = readJsonFile(inputSchemaPath);
2180
+ const outputJsonSchema = readJsonFile(outputSchemaPath);
2181
+ const sampleRow = readJsonFile(sampleRowPath);
2182
+ const expandedScreenshotPath = screenshotPath ? expandHomePath(screenshotPath) : undefined;
2183
+ const result = await runLocalAppPreview({
2184
+ filePath: expandedFilePath,
2185
+ inputSchema: inputJsonSchema,
2186
+ outputSchema: outputJsonSchema,
2187
+ sampleRow,
2188
+ screenshotPath: expandedScreenshotPath,
2189
+ componentName: component,
2190
+ headed,
2191
+ keepOpen,
2192
+ });
2193
+ printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
2194
+ if (result.screenshotPath) {
2195
+ printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
2196
+ }
2197
+ if (headed || keepOpen) {
2198
+ printLine('Headed preview is running. Close Chromium or stop the command when you are done.');
2199
+ }
2200
+ if (result.warnings.length > 0) {
2201
+ printLine(`Warnings: ${sanitizeTerminalText(result.warnings.join('; '))}`);
2202
+ }
2203
+ }
1964
2204
  async function updateAppFromFile() {
1965
2205
  const filePath = getArg('--file');
1966
2206
  const inputSchemaPath = getArg('--input-schema');
@@ -2185,6 +2425,14 @@ async function updateTaskStatus(targetStatus) {
2185
2425
  const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused';
2186
2426
  printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
2187
2427
  }
2428
+ function defaultAppExportFilename(appName, versionNum) {
2429
+ const safeName = appName
2430
+ .trim()
2431
+ .replace(/[^A-Za-z0-9._-]+/g, '_')
2432
+ .replace(/^_+|_+$/g, '')
2433
+ .slice(0, 100);
2434
+ return `${safeName || 'app'}.v${versionNum}.tsx`;
2435
+ }
2188
2436
  async function appDetail() {
2189
2437
  const appId = getArg('--app');
2190
2438
  const project = getArg('--project');
@@ -2223,6 +2471,40 @@ async function appDetail() {
2223
2471
  printLine(` Created: ${sanitizeTerminalText(detail.createdAt)}`);
2224
2472
  printLine(` Updated: ${sanitizeTerminalText(detail.updatedAt)}`);
2225
2473
  }
2474
+ async function exportAppSource() {
2475
+ const project = getArg('--project');
2476
+ const versionArg = getArg('--version');
2477
+ const outPathArg = getArg('--out');
2478
+ let appId = getArg('--app');
2479
+ const versionNum = versionArg && /^[1-9]\d*$/.test(versionArg)
2480
+ ? Number.parseInt(versionArg, 10)
2481
+ : undefined;
2482
+ if (versionArg && versionNum === undefined) {
2483
+ throw new Error('--version must be a positive integer');
2484
+ }
2485
+ if (!appId) {
2486
+ const selected = await selectAppIdInteractively(project);
2487
+ appId = selected.appId;
2488
+ }
2489
+ const query = new URLSearchParams();
2490
+ if (project) {
2491
+ query.set('project', project);
2492
+ }
2493
+ if (versionNum) {
2494
+ query.set('version', String(versionNum));
2495
+ }
2496
+ const suffix = query.toString() ? `?${query.toString()}` : '';
2497
+ const response = await authedFetch(`/api/cli/apps/${encodeURIComponent(appId)}/export${suffix}`);
2498
+ if (!response.ok) {
2499
+ throw new Error(`Failed to export app (${response.status}): ${await response.text()}`);
2500
+ }
2501
+ const data = await parseJsonResponse(response, 'App export');
2502
+ const filename = outPathArg
2503
+ ? expandHomePath(outPathArg)
2504
+ : defaultAppExportFilename(data.app.name, data.version.versionNum);
2505
+ writeTextFileEnsuringDir(filename, data.version.code);
2506
+ printLine(`Saved app ${sanitizeTerminalText(data.app.name)} v${data.version.versionNum} source to ${sanitizeTerminalText(filename)}`);
2507
+ }
2226
2508
  async function listTeamMembers() {
2227
2509
  const teamSlug = await resolveTeamSlug(getArg('--team'));
2228
2510
  printTeamMembers(await fetchTeamMembers(teamSlug));
@@ -2287,7 +2569,7 @@ async function changeTeamMemberRole() {
2287
2569
  }
2288
2570
  printLine(`Updated ${sanitizeTerminalText(member.email)} role to ${sanitizeTerminalText(role)}`);
2289
2571
  }
2290
- async function createDatasetFromRows(project, name, sourceType, rows) {
2572
+ async function createDatasetFromRows(project, name, sourceType, rows, readmeMarkdown = null) {
2291
2573
  const response = await authedFetch('/api/cli/datasets/upload', {
2292
2574
  method: 'POST',
2293
2575
  headers: {
@@ -2298,6 +2580,7 @@ async function createDatasetFromRows(project, name, sourceType, rows) {
2298
2580
  name,
2299
2581
  rows,
2300
2582
  sourceType,
2583
+ ...(readmeMarkdown !== null ? { readmeMarkdown } : {}),
2301
2584
  }),
2302
2585
  });
2303
2586
  if (!response.ok) {
@@ -2306,7 +2589,7 @@ async function createDatasetFromRows(project, name, sourceType, rows) {
2306
2589
  }
2307
2590
  return parseJsonResponse(response, 'Dataset upload');
2308
2591
  }
2309
- async function uploadJsonlDatasetInChunks(file, project, datasetName) {
2592
+ async function uploadJsonlDatasetInChunks(file, project, datasetName, readmeMarkdown = null) {
2310
2593
  let dataset = null;
2311
2594
  let totalUploaded = 0;
2312
2595
  let chunkIndex = 0;
@@ -2334,7 +2617,7 @@ async function uploadJsonlDatasetInChunks(file, project, datasetName) {
2334
2617
  printLine(`Uploading chunk ${chunkIndex} (${chunk.length} rows)...`);
2335
2618
  try {
2336
2619
  if (!dataset) {
2337
- const data = await createDatasetFromRows(project, datasetName, 'jsonl', chunk);
2620
+ const data = await createDatasetFromRows(project, datasetName, 'jsonl', chunk, readmeMarkdown);
2338
2621
  dataset = data.dataset;
2339
2622
  totalUploaded = data.dataset.rowCount;
2340
2623
  continue;
@@ -2369,18 +2652,20 @@ async function uploadDataset() {
2369
2652
  const projectArg = getArg('--project');
2370
2653
  const fileArg = getArg('--file');
2371
2654
  const name = getArg('--name');
2655
+ const usage = 'Usage: orizu datasets upload --file <path> [--project <team/project>] [--name <name>] [--readme-file <README.md> | --readme-text <markdown>]';
2372
2656
  if (!fileArg) {
2373
- throw new Error('Usage: orizu datasets upload --file <path> [--project <team/project>] [--name <name>]');
2657
+ throw new Error(usage);
2374
2658
  }
2659
+ const readmeMarkdown = readReadmeMarkdownFromArgs(usage);
2375
2660
  const file = expandHomePath(fileArg);
2376
2661
  const project = await resolveProjectSlug(projectArg);
2377
2662
  const datasetName = name || basename(file);
2378
2663
  if (extname(file).toLowerCase() === '.jsonl') {
2379
- await uploadJsonlDatasetInChunks(file, project, datasetName);
2664
+ await uploadJsonlDatasetInChunks(file, project, datasetName, readmeMarkdown);
2380
2665
  return;
2381
2666
  }
2382
2667
  const { rows, sourceType } = parseDatasetFile(file);
2383
- const data = await createDatasetFromRows(project, datasetName, sourceType, rows);
2668
+ const data = await createDatasetFromRows(project, datasetName, sourceType, rows, readmeMarkdown);
2384
2669
  printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`);
2385
2670
  if (data.dataset.url) {
2386
2671
  printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`);
@@ -2390,20 +2675,26 @@ async function pushDataset() {
2390
2675
  const projectArg = getArg('--project');
2391
2676
  const fileArg = getPositionalArg(2) || getArg('--file');
2392
2677
  const name = getArg('--name');
2678
+ const usage = 'Usage: orizu datasets push <rows.csv|rows.json|rows.jsonl> [--project <team/project>] [--name <name>] [--readme-file <README.md> | --readme-text <markdown>] [--json]';
2393
2679
  if (!fileArg) {
2394
- throw new Error('Usage: orizu datasets push <rows.csv|rows.json|rows.jsonl> [--project <team/project>] [--name <name>] [--json]');
2680
+ throw new Error(usage);
2395
2681
  }
2682
+ const readmeMarkdown = readReadmeMarkdownFromArgs(usage);
2396
2683
  const file = expandHomePath(fileArg);
2397
2684
  const project = await resolveProjectSlug(projectArg);
2398
2685
  const datasetName = name || basename(file);
2399
2686
  const { rows, sourceType } = parseDatasetFile(file);
2400
- const data = await createDatasetFromRows(project, datasetName, sourceType, rows);
2687
+ const data = await createDatasetFromRows(project, datasetName, sourceType, rows, readmeMarkdown);
2401
2688
  if (hasJsonFlag()) {
2402
- printJson({
2689
+ const payload = {
2403
2690
  dataset_id: data.dataset.id,
2404
2691
  name: data.dataset.name,
2405
2692
  row_count: data.dataset.rowCount,
2406
- });
2693
+ };
2694
+ if (data.readmeVersion?.id) {
2695
+ payload.readme_version_id = data.readmeVersion.id;
2696
+ }
2697
+ printJson(payload);
2407
2698
  return;
2408
2699
  }
2409
2700
  printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`);
@@ -2411,6 +2702,38 @@ async function pushDataset() {
2411
2702
  printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`);
2412
2703
  }
2413
2704
  }
2705
+ async function setDatasetReadme() {
2706
+ const datasetId = getPositionalArg(3) || getArg('--dataset');
2707
+ const project = getArg('--project');
2708
+ const usage = 'Usage: orizu datasets readme set <datasetId|dataset-name> [--project <team/project>] (--readme-file <README.md> | --readme-text <markdown>) [--json]';
2709
+ if (!datasetId) {
2710
+ throw new Error(usage);
2711
+ }
2712
+ const readmeMarkdown = readReadmeMarkdownFromArgs(usage);
2713
+ if (readmeMarkdown === null) {
2714
+ throw new Error(usage);
2715
+ }
2716
+ const resolvedDatasetId = await resolveDatasetIdForProject(datasetId, project);
2717
+ const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(resolvedDatasetId)}/readme`, {
2718
+ method: 'POST',
2719
+ headers: { 'Content-Type': 'application/json' },
2720
+ body: JSON.stringify({ markdown: readmeMarkdown }),
2721
+ });
2722
+ if (!response.ok) {
2723
+ throw new Error(`Failed to save dataset README: ${await response.text()}`);
2724
+ }
2725
+ const data = await parseJsonResponse(response, 'Dataset README save');
2726
+ if (hasJsonFlag()) {
2727
+ printJson({
2728
+ dataset_id: data.version.dataset_id,
2729
+ readme_version_id: data.version.id,
2730
+ readme_version_num: data.version.version_num,
2731
+ });
2732
+ return;
2733
+ }
2734
+ printLine(`Saved README v${data.version.version_num} for dataset ` +
2735
+ `${sanitizeTerminalText(data.version.dataset_id)} (${sanitizeTerminalText(data.version.id)}).`);
2736
+ }
2414
2737
  function getDatasetReferenceInput() {
2415
2738
  const fromFlag = getArg('--dataset');
2416
2739
  if (fromFlag) {
@@ -3094,6 +3417,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3094
3417
  await setScorerLabel();
3095
3418
  return;
3096
3419
  }
3420
+ if (command === 'scorers' && subcommand === 'exec') {
3421
+ await execScorer();
3422
+ return;
3423
+ }
3097
3424
  if (command === 'runners' && subcommand === 'push') {
3098
3425
  await pushRunnerArtifact('runner');
3099
3426
  return;
@@ -3143,6 +3470,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3143
3470
  await createAppFromFile();
3144
3471
  return;
3145
3472
  }
3473
+ if (command === 'apps' && subcommand === 'preview') {
3474
+ await previewAppFromFile();
3475
+ return;
3476
+ }
3146
3477
  if (command === 'apps' && subcommand === 'update') {
3147
3478
  await updateAppFromFile();
3148
3479
  return;
@@ -3155,6 +3486,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3155
3486
  await appDetail();
3156
3487
  return;
3157
3488
  }
3489
+ if (command === 'apps' && subcommand === 'export') {
3490
+ await exportAppSource();
3491
+ return;
3492
+ }
3158
3493
  if (command === 'tasks' && subcommand === 'list') {
3159
3494
  await listTasks();
3160
3495
  return;
@@ -3196,6 +3531,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3196
3531
  return;
3197
3532
  }
3198
3533
  const datasetsAction = cliArgs[2];
3534
+ if (command === 'datasets' && subcommand === 'readme' && datasetsAction === 'set') {
3535
+ await setDatasetReadme();
3536
+ return;
3537
+ }
3199
3538
  if (command === 'datasets' && subcommand === 'versions' && datasetsAction === 'create') {
3200
3539
  await createDatasetVersion();
3201
3540
  return;