orizu 0.3.4 → 0.3.6

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/dist/index.js CHANGED
@@ -13,8 +13,12 @@ import { clearServerCredentials, getServerCredentials, saveServerCredentials } f
13
13
  import { parseDatasetFile } from './file-parser.js';
14
14
  import { streamJsonlRowChunks } from './jsonl-stream.js';
15
15
  import { parseDatasetReference } from './dataset-download.js';
16
+ import { extractErrorMessage } from './error-response.js';
16
17
  import { parseGlobalFlags } from './global-flags.js';
18
+ import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js';
19
+ import { runLocalAppPreview } from './preview-runtime.js';
17
20
  import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
21
+ import { getSkillInstallPath, installSkillTarget, isSkillInstallTarget, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
18
22
  import { formatTaskCreateError } from './task-create-error.js';
19
23
  const MAX_README_LENGTH = 200_000;
20
24
  function getCliVersion() {
@@ -31,12 +35,16 @@ function printVersion() {
31
35
  printLine(`orizu ${getCliVersion()}`);
32
36
  }
33
37
  function printUsage() {
34
- 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 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>]`);
38
+ printLine(renderRootHelp());
39
+ }
40
+ function printPreviewUsage() {
41
+ 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>]`);
35
42
  }
36
43
  function printOptimizationUsage() {
37
- 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]`);
44
+ 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] [--no-skip-perfect-parent-reflection]\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]`);
38
45
  }
39
46
  let cliArgs = process.argv.slice(2);
47
+ let cliJsonOutput = false;
40
48
  function getArg(name) {
41
49
  const index = cliArgs.indexOf(name);
42
50
  if (index === -1 || index + 1 >= cliArgs.length) {
@@ -44,6 +52,26 @@ function getArg(name) {
44
52
  }
45
53
  return cliArgs[index + 1];
46
54
  }
55
+ function getArgs(name) {
56
+ const values = [];
57
+ for (let index = 0; index < cliArgs.length; index += 1) {
58
+ if (cliArgs[index] !== name) {
59
+ continue;
60
+ }
61
+ const value = cliArgs[index + 1];
62
+ if (!value || value.startsWith('-')) {
63
+ throw new Error(`Usage: ${name} <value>`);
64
+ }
65
+ values.push(value);
66
+ index += 1;
67
+ }
68
+ return values;
69
+ }
70
+ function rejectDashPrefixedOptionValue(name, value) {
71
+ if (value && value.startsWith('-')) {
72
+ throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
73
+ }
74
+ }
47
75
  export function normalizeSlugInput(slug) {
48
76
  return slug.trim().toLowerCase();
49
77
  }
@@ -1384,17 +1412,22 @@ async function submitScoreResults() {
1384
1412
  const optimizationRunId = getArg('--optimization-run');
1385
1413
  const candidateId = getArg('--candidate');
1386
1414
  if (!resultsPath || !scorerVersionId || (!subjectPromptVersionId && (!optimizationRunId || !candidateId))) {
1387
- 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]');
1415
+ 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]');
1388
1416
  }
1389
1417
  const resultBytes = readSourceBytes(resultsPath);
1390
1418
  const raw = resultsPath.endsWith('.gz')
1391
1419
  ? gunzipSync(resultBytes).toString('utf8')
1392
1420
  : resultBytes.toString('utf8');
1393
- const resultsJsonl = normalizeScoreResultsInput(resultsPath, raw);
1421
+ const scoreInput = normalizeScoreResultsInput(resultsPath, raw, hasArg('--aggregate'));
1422
+ if (scoreInput.aggregateWarning) {
1423
+ console.warn(scoreInput.aggregateWarning);
1424
+ }
1425
+ const aggregatePayload = scoreInput.aggregate || {};
1394
1426
  const response = await authedFetch(`/api/cli/scores/submit?project=${encodeURIComponent(project)}`, {
1395
1427
  method: 'POST',
1396
1428
  headers: { 'Content-Type': 'application/json' },
1397
1429
  body: JSON.stringify({
1430
+ ...aggregatePayload,
1398
1431
  scorerVersionId,
1399
1432
  subjectPromptVersionId: subjectPromptVersionId || undefined,
1400
1433
  datasetVersionId: getArg('--dataset-version') || undefined,
@@ -1402,7 +1435,7 @@ async function submitScoreResults() {
1402
1435
  splitName: getArg('--split') || undefined,
1403
1436
  optimizationRunId: optimizationRunId || undefined,
1404
1437
  candidateId: candidateId || undefined,
1405
- resultsJsonl,
1438
+ resultsJsonl: scoreInput.resultsJsonl,
1406
1439
  }),
1407
1440
  });
1408
1441
  if (!response.ok) {
@@ -1416,24 +1449,150 @@ async function submitScoreResults() {
1416
1449
  printLine(`Submitted score ${sanitizeTerminalText(data.scoreRun.id)} ` +
1417
1450
  `(${formatPercent(data.scoreRun.scoreValue)})`);
1418
1451
  }
1419
- function normalizeScoreResultsInput(sourcePath, raw) {
1452
+ function looksLikeAggregateScoreObject(value) {
1453
+ return (value.scoreValue !== undefined ||
1454
+ value.score_value !== undefined ||
1455
+ value.diagnostics !== undefined ||
1456
+ value.feedbackSummary !== undefined ||
1457
+ value.feedback_summary !== undefined ||
1458
+ value.rowEvidence !== undefined ||
1459
+ value.row_evidence !== undefined ||
1460
+ value.dependencyScoreRunIds !== undefined ||
1461
+ value.dependency_score_run_ids !== undefined);
1462
+ }
1463
+ function numberFromUnknown(value) {
1464
+ if (typeof value === 'number' && Number.isFinite(value))
1465
+ return value;
1466
+ if (typeof value === 'string' && value.trim()) {
1467
+ const parsed = Number(value);
1468
+ return Number.isFinite(parsed) ? parsed : null;
1469
+ }
1470
+ if (typeof value === 'boolean')
1471
+ return value ? 1 : 0;
1472
+ return null;
1473
+ }
1474
+ function normalizeScoreResultsInput(sourcePath, raw, aggregateMode = false) {
1420
1475
  const logicalPath = sourcePath.endsWith('.gz') ? sourcePath.slice(0, -3) : sourcePath;
1421
1476
  if (!logicalPath.endsWith('.json'))
1422
- return raw;
1477
+ return { resultsJsonl: raw };
1423
1478
  try {
1424
1479
  const parsed = JSON.parse(raw);
1480
+ if (aggregateMode) {
1481
+ if (!isRecord(parsed)) {
1482
+ throw new Error('Aggregate score results JSON must be an object');
1483
+ }
1484
+ return { aggregate: parsed };
1485
+ }
1486
+ if (isRecord(parsed) && looksLikeAggregateScoreObject(parsed)) {
1487
+ return {
1488
+ aggregate: parsed,
1489
+ aggregateWarning: 'Warning: aggregate score JSON detected. Pass --aggregate to make this explicit.',
1490
+ };
1491
+ }
1425
1492
  const rows = Array.isArray(parsed) ? parsed : [parsed];
1426
1493
  if (rows.some(row => !isRecord(row))) {
1427
1494
  throw new Error('JSON score results must be an object or an array of objects');
1428
1495
  }
1429
- return rows.map(row => JSON.stringify(row)).join('\n');
1496
+ return { resultsJsonl: rows.map(row => JSON.stringify(row)).join('\n') };
1430
1497
  }
1431
1498
  catch (error) {
1432
1499
  const message = error instanceof Error ? error.message : String(error);
1433
1500
  throw new Error(`Invalid score results JSON '${sourcePath}': ${message}`);
1434
1501
  }
1435
1502
  }
1436
- const OPTIMIZATION_REPORT_MAX_BYTES = 2 * 1024 * 1024;
1503
+ function readOptionalJsonlFile(pathArg) {
1504
+ if (!pathArg)
1505
+ return undefined;
1506
+ const bytes = readSourceBytes(pathArg);
1507
+ return pathArg.endsWith('.gz')
1508
+ ? gunzipSync(bytes).toString('utf8')
1509
+ : bytes.toString('utf8');
1510
+ }
1511
+ function parseAliasAssignments(value) {
1512
+ const result = {};
1513
+ if (!value)
1514
+ return result;
1515
+ for (const item of parseCommaSeparated(value)) {
1516
+ const equalsIndex = item.indexOf('=');
1517
+ if (equalsIndex <= 0 || equalsIndex === item.length - 1) {
1518
+ throw new Error(`Expected alias assignment in form alias=value, got '${item}'`);
1519
+ }
1520
+ result[item.slice(0, equalsIndex)] = item.slice(equalsIndex + 1);
1521
+ }
1522
+ return result;
1523
+ }
1524
+ function readDependencyResultAssignments(value) {
1525
+ const assignments = parseAliasAssignments(value);
1526
+ const result = {};
1527
+ for (const [alias, path] of Object.entries(assignments)) {
1528
+ result[alias] = readOptionalJsonlFile(path) || '';
1529
+ }
1530
+ return Object.keys(result).length > 0 ? result : undefined;
1531
+ }
1532
+ async function execScorer() {
1533
+ const project = getArg('--project') || await resolveProjectSlug(null);
1534
+ const scorerVersionId = getArg('--scorer-version');
1535
+ const subjectPromptVersionId = getArg('--subject-version') || getArg('--prompt-version');
1536
+ const optimizationRunId = getArg('--optimization-run');
1537
+ const candidateId = getArg('--candidate');
1538
+ const datasetVersionId = getArg('--dataset-version');
1539
+ const splitSetId = getArg('--split-set');
1540
+ const splitName = getArg('--split');
1541
+ const outPath = getArg('--out');
1542
+ if (!scorerVersionId ||
1543
+ (!subjectPromptVersionId && (!optimizationRunId || !candidateId)) ||
1544
+ !datasetVersionId ||
1545
+ !splitSetId ||
1546
+ !splitName) {
1547
+ 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]');
1548
+ }
1549
+ const response = await authedFetch(`/api/cli/scorers/exec?project=${encodeURIComponent(project)}`, {
1550
+ method: 'POST',
1551
+ headers: { 'Content-Type': 'application/json' },
1552
+ body: JSON.stringify({
1553
+ scorerVersionId,
1554
+ subjectPromptVersionId: subjectPromptVersionId || undefined,
1555
+ optimizationRunId: optimizationRunId || undefined,
1556
+ candidateId: candidateId || undefined,
1557
+ datasetVersionId,
1558
+ splitSetId,
1559
+ splitName,
1560
+ submit: !hasArg('--no-submit'),
1561
+ reuseExisting: !hasArg('--no-reuse-existing'),
1562
+ subjectResultsJsonl: readOptionalJsonlFile(getArg('--subject-results') || getArg('--outputs')),
1563
+ dependencyScoreRunIds: parseAliasAssignments(getArg('--dependency-score-run') || getArg('--dependency-score-runs')),
1564
+ dependencyResultsJsonl: readDependencyResultAssignments(getArg('--dependency-results')),
1565
+ }),
1566
+ });
1567
+ if (!response.ok) {
1568
+ throw new Error(`Failed to execute scorer: ${await response.text()}`);
1569
+ }
1570
+ const data = await parseJsonResponse(response, 'Scorer exec');
1571
+ if (outPath) {
1572
+ writeFileSync(expandHomePath(outPath), `${JSON.stringify(data.scoreResult || data, null, 2)}\n`);
1573
+ }
1574
+ if (hasJsonFlag()) {
1575
+ printJson(data);
1576
+ return;
1577
+ }
1578
+ const scoreRun = isRecord(data.scoreRun) ? data.scoreRun : null;
1579
+ const scoreResult = isRecord(data.scoreResult) ? data.scoreResult : null;
1580
+ const scoreValue = scoreRun
1581
+ ? scoreRun.scoreValue
1582
+ : scoreResult
1583
+ ? scoreResult.scoreValue
1584
+ : null;
1585
+ if (scoreRun && typeof scoreRun.id === 'string') {
1586
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} -> score ${sanitizeTerminalText(scoreRun.id)} (${formatPercent(numberFromUnknown(scoreValue))})`);
1587
+ }
1588
+ else {
1589
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} (${formatPercent(numberFromUnknown(scoreValue))})`);
1590
+ }
1591
+ if (outPath) {
1592
+ printLine(`Wrote scorer result to ${sanitizeTerminalText(outPath)}`);
1593
+ }
1594
+ }
1595
+ const MARKDOWN_REPORT_MAX_BYTES = 2 * 1024 * 1024;
1437
1596
  function parseOptionalNumberFlag(name) {
1438
1597
  const value = getArg(name);
1439
1598
  if (!value) {
@@ -1448,9 +1607,11 @@ function parseOptionalNumberFlag(name) {
1448
1607
  function hasObjectKeys(value) {
1449
1608
  return Object.keys(value).length > 0;
1450
1609
  }
1451
- function readOptimizationReportInput() {
1610
+ function readMarkdownReportInput(reportLabel) {
1452
1611
  const report = getArg('--report');
1453
1612
  const reportFile = getArg('--report-file');
1613
+ rejectDashPrefixedOptionValue('--report', report);
1614
+ rejectDashPrefixedOptionValue('--report-file', reportFile);
1454
1615
  if (report && reportFile) {
1455
1616
  throw new Error('Use either --report or --report-file, not both');
1456
1617
  }
@@ -1475,13 +1636,19 @@ function readOptimizationReportInput() {
1475
1636
  sourceName = 'inline';
1476
1637
  }
1477
1638
  if (!markdown.trim()) {
1478
- throw new Error('Optimization report markdown must not be blank');
1639
+ throw new Error(`${reportLabel} report markdown must not be blank`);
1479
1640
  }
1480
- if (Buffer.byteLength(markdown, 'utf8') > OPTIMIZATION_REPORT_MAX_BYTES) {
1481
- throw new Error(`Optimization report exceeds ${OPTIMIZATION_REPORT_MAX_BYTES} bytes`);
1641
+ if (Buffer.byteLength(markdown, 'utf8') > MARKDOWN_REPORT_MAX_BYTES) {
1642
+ throw new Error(`${reportLabel} report exceeds ${MARKDOWN_REPORT_MAX_BYTES} bytes`);
1482
1643
  }
1483
1644
  return { markdown, sourceName };
1484
1645
  }
1646
+ function readOptimizationReportInput() {
1647
+ return readMarkdownReportInput('Optimization');
1648
+ }
1649
+ function readTaskReportInput() {
1650
+ return readMarkdownReportInput('Task');
1651
+ }
1485
1652
  async function startOptimizationRun() {
1486
1653
  const project = getArg('--project') || await resolveProjectSlug(null);
1487
1654
  const optimizerVersionId = getArg('--optimizer-version');
@@ -1786,11 +1953,106 @@ function readSourceBytes(pathArg) {
1786
1953
  }
1787
1954
  }
1788
1955
  function hasJsonFlag() {
1789
- return hasArg('--json');
1956
+ return cliJsonOutput || hasArg('--json');
1790
1957
  }
1791
1958
  function printJson(value) {
1792
1959
  printLine(JSON.stringify(value));
1793
1960
  }
1961
+ function printCapabilities() {
1962
+ const capabilities = getCapabilities(getCliVersion());
1963
+ if (hasJsonFlag()) {
1964
+ printLine(JSON.stringify(capabilities));
1965
+ return;
1966
+ }
1967
+ printLine('orizu capabilities');
1968
+ printLine('');
1969
+ printLine('Run `orizu capabilities --json` for a machine-readable command manifest.');
1970
+ printLine('');
1971
+ for (const command of capabilities.commands) {
1972
+ printLine(` ${command.name.padEnd(34)} ${command.help}`);
1973
+ }
1974
+ }
1975
+ async function askYesNo(question, defaultYes) {
1976
+ const rl = createInterface({ input, output });
1977
+ try {
1978
+ const suffix = defaultYes ? 'Y/n' : 'y/N';
1979
+ const answer = (await rl.question(`${question} (${suffix}) `)).trim().toLowerCase();
1980
+ if (!answer) {
1981
+ return defaultYes;
1982
+ }
1983
+ return answer === 'y' || answer === 'yes';
1984
+ }
1985
+ finally {
1986
+ rl.close();
1987
+ }
1988
+ }
1989
+ async function promptSkillInstallTargets() {
1990
+ if (!isInteractiveTerminal()) {
1991
+ throw new Error(`Usage: orizu install-skill --target <${SKILL_INSTALL_TARGETS.join('|')}> [--yes] [--dry-run]`);
1992
+ }
1993
+ printLine('Where should the Orizu CLI skill be installed?');
1994
+ const targets = [];
1995
+ for (const target of SKILL_INSTALL_TARGETS) {
1996
+ const defaultYes = target === 'agent-user';
1997
+ if (await askYesNo(` Install to ${target}?`, defaultYes)) {
1998
+ targets.push(target);
1999
+ }
2000
+ }
2001
+ return targets;
2002
+ }
2003
+ function parseSkillInstallTargets() {
2004
+ const rawTargets = getArgs('--target');
2005
+ const targets = [];
2006
+ for (const target of rawTargets) {
2007
+ if (!isSkillInstallTarget(target)) {
2008
+ throw new Error(`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`);
2009
+ }
2010
+ if (!targets.includes(target)) {
2011
+ targets.push(target);
2012
+ }
2013
+ }
2014
+ return targets;
2015
+ }
2016
+ function formatSkillInstallAction(action) {
2017
+ if (action === 'created')
2018
+ return 'Wrote';
2019
+ if (action === 'updated')
2020
+ return 'Updated';
2021
+ if (action === 'would-create')
2022
+ return 'Would write';
2023
+ return 'Would update';
2024
+ }
2025
+ async function installSkillCommand() {
2026
+ const skipConfirm = hasArg('--yes');
2027
+ const dryRun = hasArg('--dry-run');
2028
+ let targets = parseSkillInstallTargets();
2029
+ if (targets.length === 0) {
2030
+ targets = await promptSkillInstallTargets();
2031
+ }
2032
+ if (targets.length === 0) {
2033
+ printLine('No targets selected; nothing to do.');
2034
+ return;
2035
+ }
2036
+ for (const target of targets) {
2037
+ let overwrite = skipConfirm;
2038
+ const installPath = getSkillInstallPath(target);
2039
+ if (!dryRun && !overwrite && targetNeedsOverwrite(target)) {
2040
+ if (!isInteractiveTerminal()) {
2041
+ throw new Error(`${installPath} already exists. Pass --yes to replace it.`);
2042
+ }
2043
+ overwrite = await askYesNo(`Replace ${installPath}?`, true);
2044
+ if (!overwrite) {
2045
+ printLine(`Skipped ${installPath}`);
2046
+ continue;
2047
+ }
2048
+ }
2049
+ const result = installSkillTarget(target, {
2050
+ overwrite,
2051
+ dryRun,
2052
+ });
2053
+ printLine(`${formatSkillInstallAction(result.action)} ${result.path}`);
2054
+ }
2055
+ }
1794
2056
  function readJsonFile(pathArg) {
1795
2057
  const raw = readSourceFile(pathArg);
1796
2058
  try {
@@ -2020,10 +2282,51 @@ async function createAppFromFile() {
2020
2282
  }
2021
2283
  const data = await parseJsonResponse(response, 'App create');
2022
2284
  printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`);
2285
+ if (data.app.url) {
2286
+ printLine(`View app: ${formatTerminalLink(data.app.url)}`);
2287
+ }
2023
2288
  if (data.warnings?.length) {
2024
2289
  printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`);
2025
2290
  }
2026
2291
  }
2292
+ async function previewAppFromFile() {
2293
+ const filePath = getArg('--file');
2294
+ const inputSchemaPath = getArg('--input-schema');
2295
+ const outputSchemaPath = getArg('--output-schema');
2296
+ const sampleRowPath = getArg('--sample-row');
2297
+ const screenshotPath = getArg('--screenshot');
2298
+ const component = getArg('--component') || undefined;
2299
+ const headed = hasArg('--headed');
2300
+ const keepOpen = hasArg('--keep-open');
2301
+ if (!filePath || !inputSchemaPath || !outputSchemaPath || !sampleRowPath) {
2302
+ 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>]');
2303
+ }
2304
+ const expandedFilePath = realpathSync(expandHomePath(filePath));
2305
+ const inputJsonSchema = readJsonFile(inputSchemaPath);
2306
+ const outputJsonSchema = readJsonFile(outputSchemaPath);
2307
+ const sampleRow = readJsonFile(sampleRowPath);
2308
+ const expandedScreenshotPath = screenshotPath ? expandHomePath(screenshotPath) : undefined;
2309
+ const result = await runLocalAppPreview({
2310
+ filePath: expandedFilePath,
2311
+ inputSchema: inputJsonSchema,
2312
+ outputSchema: outputJsonSchema,
2313
+ sampleRow,
2314
+ screenshotPath: expandedScreenshotPath,
2315
+ componentName: component,
2316
+ headed,
2317
+ keepOpen,
2318
+ });
2319
+ printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
2320
+ if (result.screenshotPath) {
2321
+ printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
2322
+ }
2323
+ if (headed || keepOpen) {
2324
+ printLine('Headed preview is running. Close Chromium or stop the command when you are done.');
2325
+ }
2326
+ if (result.warnings.length > 0) {
2327
+ printLine(`Warnings: ${sanitizeTerminalText(result.warnings.join('; '))}`);
2328
+ }
2329
+ }
2027
2330
  async function updateAppFromFile() {
2028
2331
  const filePath = getArg('--file');
2029
2332
  const inputSchemaPath = getArg('--input-schema');
@@ -2106,14 +2409,15 @@ async function createTask() {
2106
2409
  const appId = getArg('--app');
2107
2410
  const title = getArg('--title');
2108
2411
  const assignees = parseCommaSeparated(getArg('--assignees'));
2412
+ const publish = hasArg('--publish');
2109
2413
  const versionArg = getArg('--version');
2110
2414
  const instructions = getArg('--instructions');
2111
2415
  const labelsPerItemArg = getArg('--labels-per-item');
2112
2416
  const labelsPerItem = labelsPerItemArg ? Number(labelsPerItemArg) : 1;
2113
2417
  const parsedVersionNum = versionArg ? Number(versionArg) : Number.NaN;
2114
2418
  const versionNum = Number.isInteger(parsedVersionNum) && parsedVersionNum > 0 ? parsedVersionNum : null;
2115
- if (!projectSlug || !datasetId || !appId || !title || assignees.length === 0) {
2116
- throw new Error('Usage: orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> --assignees <userIdOrEmail1,userIdOrEmail2> [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]');
2419
+ if (!projectSlug || !datasetId || !appId || !title || (publish && assignees.length === 0)) {
2420
+ throw new Error('Usage: orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> [--assignees <userIdOrEmail1,userIdOrEmail2>] [--publish] [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]');
2117
2421
  }
2118
2422
  if (versionArg && versionNum === null) {
2119
2423
  throw new Error('--version must be a positive integer');
@@ -2128,6 +2432,7 @@ async function createTask() {
2128
2432
  versionNum,
2129
2433
  title,
2130
2434
  memberIds: assignees,
2435
+ publish,
2131
2436
  instructions,
2132
2437
  requiredAssignmentsPerRow: labelsPerItem,
2133
2438
  }),
@@ -2155,6 +2460,13 @@ async function createTask() {
2155
2460
  status: data.task.status,
2156
2461
  requiredAssignmentsPerRow: data.task.requiredAssignmentsPerRow,
2157
2462
  assignmentsCreated: data.assignmentsCreated,
2463
+ draft: data.task.status === 'draft',
2464
+ ...(data.task.status === 'draft'
2465
+ ? {
2466
+ message: 'Draft created. Test it manually before assigning.',
2467
+ publishCommand: `orizu tasks publish --task ${data.task.id} --assignees <userId1,userId2>`,
2468
+ }
2469
+ : {}),
2158
2470
  ...(data.assignmentShortfall !== undefined ? { assignmentShortfall: data.assignmentShortfall } : {}),
2159
2471
  ...(data.warning ? { warning: data.warning } : {}),
2160
2472
  }, null, 2));
@@ -2162,14 +2474,56 @@ async function createTask() {
2162
2474
  }
2163
2475
  const baseUrl = getBaseUrl();
2164
2476
  const taskUrl = `${baseUrl}/d/${projectSlug}/tasks/${data.task.id}`;
2165
- printLine(`Created task ${sanitizeTerminalText(data.task.title)} (${sanitizeTerminalText(data.task.id)}) [${sanitizeTerminalText(data.task.status)}]` +
2477
+ const isDraft = data.task.status === 'draft';
2478
+ printLine(`Created ${isDraft ? 'draft ' : ''}task ${sanitizeTerminalText(data.task.title)} (${sanitizeTerminalText(data.task.id)}) [${sanitizeTerminalText(data.task.status)}]` +
2166
2479
  `\n Task ID: ${sanitizeTerminalText(data.task.id)}` +
2167
2480
  `\n Dataset ID: ${sanitizeTerminalText(datasetId)}` +
2168
2481
  `\n Version: v${data.task.versionNum} (${sanitizeTerminalText(data.task.versionId)})` +
2169
2482
  `\n Labels/row: ${data.task.requiredAssignmentsPerRow}` +
2170
2483
  `\n Assignments: ${data.assignmentsCreated}` +
2171
2484
  (data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '') +
2172
- `\n URL: ${sanitizeTerminalText(taskUrl)}`);
2485
+ `\n URL: ${sanitizeTerminalText(taskUrl)}` +
2486
+ (isDraft
2487
+ ? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval: orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`
2488
+ : ''));
2489
+ }
2490
+ async function publishTask() {
2491
+ const taskId = getArg('--task');
2492
+ const assignees = parseCommaSeparated(getArg('--assignees'));
2493
+ if (!taskId || assignees.length === 0) {
2494
+ throw new Error('Usage: orizu tasks publish --task <taskId> --assignees <userId1,userId2> [--json]');
2495
+ }
2496
+ const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/publish`, {
2497
+ method: 'POST',
2498
+ headers: { 'Content-Type': 'application/json' },
2499
+ body: JSON.stringify({ memberIds: assignees }),
2500
+ });
2501
+ if (!response.ok) {
2502
+ throw new Error(`Failed to publish task: ${await extractErrorMessage(response)}`);
2503
+ }
2504
+ const data = await parseJsonResponse(response, 'Task publish');
2505
+ if (hasArg('--json')) {
2506
+ printLine(JSON.stringify({
2507
+ taskId: data.task.id,
2508
+ status: data.task.status,
2509
+ assignmentsCreated: data.assignmentsCreated,
2510
+ assignmentsProcessed: data.assignmentsProcessed,
2511
+ ...(data.assignmentsRemoved !== undefined
2512
+ ? { assignmentsRemoved: data.assignmentsRemoved }
2513
+ : {}),
2514
+ ...(data.assignmentShortfall !== undefined
2515
+ ? { assignmentShortfall: data.assignmentShortfall }
2516
+ : {}),
2517
+ ...(data.warning ? { warning: data.warning } : {}),
2518
+ }, null, 2));
2519
+ return;
2520
+ }
2521
+ printLine(`Published task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]` +
2522
+ `\n Assignments: ${data.assignmentsCreated}` +
2523
+ (data.assignmentsRemoved !== undefined
2524
+ ? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
2525
+ : '') +
2526
+ (data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : ''));
2173
2527
  }
2174
2528
  async function assignTask() {
2175
2529
  const taskId = getArg('--task');
@@ -2248,6 +2602,35 @@ async function updateTaskStatus(targetStatus) {
2248
2602
  const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused';
2249
2603
  printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
2250
2604
  }
2605
+ async function setTaskReport() {
2606
+ const taskId = getArg('--task');
2607
+ rejectDashPrefixedOptionValue('--task', taskId);
2608
+ if (!taskId) {
2609
+ throw new Error('Usage: orizu tasks report set --task <taskId> (--report <markdown|@file> | --report-file <path>) [--json]');
2610
+ }
2611
+ const report = readTaskReportInput();
2612
+ if (!report) {
2613
+ throw new Error('Usage: orizu tasks report set --task <taskId> (--report <markdown|@file> | --report-file <path>) [--json]');
2614
+ }
2615
+ const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/report`, {
2616
+ method: 'PATCH',
2617
+ headers: { 'Content-Type': 'application/json' },
2618
+ body: JSON.stringify({
2619
+ reportMarkdown: report.markdown,
2620
+ reportSourceName: report.sourceName,
2621
+ }),
2622
+ });
2623
+ if (!response.ok) {
2624
+ throw new Error(`Failed to update task report: ${await response.text()}`);
2625
+ }
2626
+ const data = await parseJsonResponse(response, 'Task report update');
2627
+ if (hasJsonFlag()) {
2628
+ printJson(data);
2629
+ return;
2630
+ }
2631
+ printLine(`Uploaded task report for ${sanitizeTerminalText(data.task.id)} ` +
2632
+ `[${sanitizeTerminalText(data.task.status)}]`);
2633
+ }
2251
2634
  function defaultAppExportFilename(appName, versionNum) {
2252
2635
  const safeName = appName
2253
2636
  .trim()
@@ -3116,10 +3499,37 @@ async function runnersExec() {
3116
3499
  materializedRunner.cleanup();
3117
3500
  }
3118
3501
  }
3502
+ function isHelpToken(value) {
3503
+ return value === '--help' || value === '-h';
3504
+ }
3505
+ function extractHelpTarget(args) {
3506
+ if (args[0] === 'help') {
3507
+ return helpTargetFromArgs(args.slice(1));
3508
+ }
3509
+ if (!args.some(isHelpToken)) {
3510
+ return null;
3511
+ }
3512
+ return helpTargetFromArgs(args);
3513
+ }
3514
+ function helpTargetFromArgs(args) {
3515
+ const targetArgs = args.filter(arg => !isHelpToken(arg) && arg !== '--json');
3516
+ const optionIndex = targetArgs.findIndex(arg => arg.startsWith('-'));
3517
+ return optionIndex === -1 ? targetArgs : targetArgs.slice(0, optionIndex);
3518
+ }
3119
3519
  export async function main(rawArgs = process.argv.slice(2)) {
3120
3520
  const parsed = parseGlobalFlags(rawArgs);
3121
3521
  setGlobalFlags(parsed.flags);
3122
3522
  cliArgs = parsed.args;
3523
+ cliJsonOutput = false;
3524
+ if (cliArgs[0] === '--json') {
3525
+ cliJsonOutput = true;
3526
+ cliArgs = cliArgs.slice(1);
3527
+ }
3528
+ const helpTarget = extractHelpTarget(cliArgs);
3529
+ if (helpTarget !== null) {
3530
+ printLine(renderHelpForArgs(helpTarget));
3531
+ return;
3532
+ }
3123
3533
  const command = cliArgs[0];
3124
3534
  const subcommand = cliArgs[1];
3125
3535
  if (command === '--version' || command === '-v') {
@@ -3128,9 +3538,20 @@ export async function main(rawArgs = process.argv.slice(2)) {
3128
3538
  }
3129
3539
  if (!command) {
3130
3540
  printUsage();
3131
- printOptimizationUsage();
3132
3541
  process.exit(1);
3133
3542
  }
3543
+ if (command === 'capabilities') {
3544
+ printCapabilities();
3545
+ return;
3546
+ }
3547
+ if (command === 'install-skill') {
3548
+ await installSkillCommand();
3549
+ return;
3550
+ }
3551
+ if (command === 'skills' && subcommand === 'install') {
3552
+ await installSkillCommand();
3553
+ return;
3554
+ }
3134
3555
  if (command === 'login') {
3135
3556
  await login();
3136
3557
  return;
@@ -3240,6 +3661,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3240
3661
  await setScorerLabel();
3241
3662
  return;
3242
3663
  }
3664
+ if (command === 'scorers' && subcommand === 'exec') {
3665
+ await execScorer();
3666
+ return;
3667
+ }
3243
3668
  if (command === 'runners' && subcommand === 'push') {
3244
3669
  await pushRunnerArtifact('runner');
3245
3670
  return;
@@ -3289,6 +3714,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3289
3714
  await createAppFromFile();
3290
3715
  return;
3291
3716
  }
3717
+ if (command === 'apps' && subcommand === 'preview') {
3718
+ await previewAppFromFile();
3719
+ return;
3720
+ }
3292
3721
  if (command === 'apps' && subcommand === 'update') {
3293
3722
  await updateAppFromFile();
3294
3723
  return;
@@ -3313,6 +3742,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
3313
3742
  await createTask();
3314
3743
  return;
3315
3744
  }
3745
+ if (command === 'tasks' && subcommand === 'publish') {
3746
+ await publishTask();
3747
+ return;
3748
+ }
3316
3749
  if (command === 'tasks' && subcommand === 'assign') {
3317
3750
  await assignTask();
3318
3751
  return;
@@ -3321,6 +3754,12 @@ export async function main(rawArgs = process.argv.slice(2)) {
3321
3754
  await taskStatus();
3322
3755
  return;
3323
3756
  }
3757
+ if (command === 'tasks' &&
3758
+ subcommand === 'report' &&
3759
+ (cliArgs[2] === 'set' || cliArgs[2] === 'upload')) {
3760
+ await setTaskReport();
3761
+ return;
3762
+ }
3324
3763
  if (command === 'tasks' && subcommand === 'pause') {
3325
3764
  await updateTaskStatus('paused');
3326
3765
  return;
@@ -3383,7 +3822,6 @@ export async function main(rawArgs = process.argv.slice(2)) {
3383
3822
  return;
3384
3823
  }
3385
3824
  printUsage();
3386
- printOptimizationUsage();
3387
3825
  process.exit(1);
3388
3826
  }
3389
3827
  function isCliEntrypoint() {