orizu 0.3.1 → 0.3.3

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
@@ -101,6 +101,17 @@ orizu logout
101
101
  <td>Create a project</td>
102
102
  <td><code>orizu projects create --name "Support QA" --team ops-eval</code></td>
103
103
  </tr>
104
+ <tr>
105
+ <td colspan="2"><strong>Prompts</strong></td>
106
+ </tr>
107
+ <tr>
108
+ <td>List prompts</td>
109
+ <td><code>orizu prompts list --project ops-eval/support-qa</code></td>
110
+ </tr>
111
+ <tr>
112
+ <td>Show prompt comments</td>
113
+ <td><code>orizu prompts comments &lt;promptIdOrName&gt; --project ops-eval/support-qa</code></td>
114
+ </tr>
104
115
  <tr>
105
116
  <td colspan="2"><strong>Datasets</strong></td>
106
117
  </tr>
package/dist/index.js CHANGED
@@ -30,10 +30,10 @@ function printVersion() {
30
30
  printLine(`orizu ${getCliVersion()}`);
31
31
  }
32
32
  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 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>]`);
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>]`);
34
34
  }
35
35
  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]\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]`);
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]`);
37
37
  }
38
38
  let cliArgs = process.argv.slice(2);
39
39
  function getArg(name) {
@@ -480,6 +480,63 @@ function printTaskStatusSummary(data) {
480
480
  });
481
481
  }
482
482
  }
483
+ function compactTerminalText(value, maxLength = 160) {
484
+ const compact = sanitizeTerminalText(value).replace(/\s+/g, ' ').trim();
485
+ if (compact.length <= maxLength) {
486
+ return compact;
487
+ }
488
+ return `${compact.slice(0, Math.max(0, maxLength - 1)).trim()}…`;
489
+ }
490
+ function printIndentedBody(value, indent) {
491
+ const lines = sanitizeTerminalText(value || '').split(/\r?\n/);
492
+ for (const line of lines) {
493
+ printLine(`${indent}${line}`);
494
+ }
495
+ }
496
+ function formatPromptCommentAnchor(anchor) {
497
+ if (!anchor) {
498
+ return null;
499
+ }
500
+ const lineLabel = anchor.startLine && anchor.endLine
501
+ ? anchor.startLine === anchor.endLine
502
+ ? `line ${anchor.startLine}`
503
+ : `lines ${anchor.startLine}-${anchor.endLine}`
504
+ : null;
505
+ const selectedText = anchor.text ? `"${compactTerminalText(anchor.text, 180)}"` : null;
506
+ if (lineLabel && selectedText) {
507
+ return `${lineLabel}: ${selectedText}`;
508
+ }
509
+ return lineLabel || selectedText;
510
+ }
511
+ function printPromptComments(data) {
512
+ const versionLabel = data.version.versionLabel || (data.version.versionNumber ? `v${data.version.versionNumber}` : data.version.id);
513
+ printLine(`Prompt: ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)})`);
514
+ printLine(`Version: ${sanitizeTerminalText(String(versionLabel))} (${sanitizeTerminalText(data.version.id)})`);
515
+ printLine(`Comments: ${data.summary.threadCount} thread${data.summary.threadCount === 1 ? '' : 's'} ` +
516
+ `(${data.summary.openThreadCount} open, ${data.summary.resolvedThreadCount} resolved, ${data.summary.replyCount} replies)`);
517
+ if (data.comments.length === 0) {
518
+ printLine('\nNo comments found for this prompt version.');
519
+ return;
520
+ }
521
+ for (const [index, thread] of data.comments.entries()) {
522
+ const authorName = thread.author?.name || 'Unknown';
523
+ const anchor = formatPromptCommentAnchor(thread.anchor);
524
+ printLine('');
525
+ printLine(`${index + 1}. [${thread.status}] ${sanitizeTerminalText(authorName)} · ${sanitizeTerminalText(thread.createdAt)}`);
526
+ if (anchor) {
527
+ printLine(` Selection: ${anchor}`);
528
+ }
529
+ printIndentedBody(thread.body, ' ');
530
+ if (thread.replies.length > 0) {
531
+ printLine(` Replies (${thread.replies.length})`);
532
+ for (const reply of thread.replies) {
533
+ const replyAuthor = reply.author?.name || 'Unknown';
534
+ printLine(` - ${sanitizeTerminalText(replyAuthor)} · ${sanitizeTerminalText(reply.createdAt)}`);
535
+ printIndentedBody(reply.body, ' ');
536
+ }
537
+ }
538
+ }
539
+ }
483
540
  const DEFAULT_AUTH_CALLBACK_PORT = 43123;
484
541
  function resolveAuthCallbackPort() {
485
542
  const envPort = process.env.ORIZU_AUTH_PORT;
@@ -788,6 +845,33 @@ async function listJudges() {
788
845
  const data = await parseJsonResponse(response, 'Judges list');
789
846
  printPromptSummaries(data.judges, 'No judges found.');
790
847
  }
848
+ async function listPromptComments() {
849
+ const promptRef = getPositionalArg(2);
850
+ const project = getArg('--project') || await resolveProjectSlug(null);
851
+ const label = getArg('--label');
852
+ const version = getArg('--version');
853
+ if (!promptRef) {
854
+ throw new Error('Usage: orizu prompts comments <prompt-id-or-name> --project <team/project> [--label <label> | --version <version-id>] [--json]');
855
+ }
856
+ if (label && version) {
857
+ throw new Error('Use either --label or --version, not both');
858
+ }
859
+ const params = new URLSearchParams({ project });
860
+ if (label)
861
+ params.set('label', label);
862
+ if (version)
863
+ params.set('version', version);
864
+ const response = await authedFetch(`/api/cli/prompts/${encodeURIComponent(promptRef)}/comments?${params.toString()}`);
865
+ if (!response.ok) {
866
+ throw new Error(`Failed to fetch prompt comments: ${await response.text()}`);
867
+ }
868
+ const data = await parseJsonResponse(response, 'Prompt comments');
869
+ if (hasJsonFlag()) {
870
+ printJson(data);
871
+ return;
872
+ }
873
+ printPromptComments(data);
874
+ }
791
875
  async function listScorers() {
792
876
  const project = getArg('--project') || await resolveProjectSlug(null);
793
877
  const response = await authedFetch(`/api/cli/scorers?project=${encodeURIComponent(project)}`);
@@ -1473,6 +1557,27 @@ async function updateOptimizationRunLifecycle(action) {
1473
1557
  printLine(`Cancelled optimization run ${id}`);
1474
1558
  }
1475
1559
  }
1560
+ async function exportOptimizationRun() {
1561
+ const runId = getPositionalArg(2) || getArg('--run-id');
1562
+ const outPathArg = getArg('--out');
1563
+ if (!runId) {
1564
+ throw new Error('Usage: orizu optimizations export <run-id> [--out <path>] [--json]');
1565
+ }
1566
+ const response = await authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/export`);
1567
+ if (!response.ok) {
1568
+ throw new Error(`Failed to export optimization run: ${await response.text()}`);
1569
+ }
1570
+ const data = await parseJsonResponse(response, 'Optimization export');
1571
+ if (hasJsonFlag() && !outPathArg) {
1572
+ printJson(data);
1573
+ return;
1574
+ }
1575
+ const filename = outPathArg
1576
+ ? expandHomePath(outPathArg)
1577
+ : `${runId}.optimization.json`;
1578
+ writeTextFileEnsuringDir(filename, `${JSON.stringify(data, null, 2)}\n`);
1579
+ printLine(`Saved optimization export to ${sanitizeTerminalText(filename)}`);
1580
+ }
1476
1581
  function removeFlagWithValue(args, flag) {
1477
1582
  const filtered = [];
1478
1583
  for (let index = 0; index < args.length; index += 1) {
@@ -1485,8 +1590,11 @@ function removeFlagWithValue(args, flag) {
1485
1590
  return filtered;
1486
1591
  }
1487
1592
  function bundledOrizuGepaPythonPath() {
1488
- const candidate = fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url));
1489
- return existsSync(candidate) ? candidate : null;
1593
+ const candidates = [
1594
+ fileURLToPath(new URL('../vendor/orizu-gepa-python/src', import.meta.url)),
1595
+ fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url)),
1596
+ ];
1597
+ return candidates.find(candidate => existsSync(candidate)) ?? null;
1490
1598
  }
1491
1599
  async function runGepaOptimization() {
1492
1600
  const project = getArg('--project') || await resolveProjectSlug(null);
@@ -2664,6 +2772,47 @@ function readRunnerManifest(runnerDir) {
2664
2772
  supports_body_kind: supportedBodyKinds,
2665
2773
  };
2666
2774
  }
2775
+ const RUNNER_TIMEOUT_MS = 120_000;
2776
+ const RUNNER_OUTPUT_MAX_BYTES = 2 * 1024 * 1024;
2777
+ const RUNNER_ARTIFACT_MAX_BYTES = 25 * 1024 * 1024;
2778
+ const RUNNER_ENV_ALLOWLIST = new Set([
2779
+ 'PATH',
2780
+ 'SystemRoot',
2781
+ 'WINDIR',
2782
+ 'HOME',
2783
+ 'TMPDIR',
2784
+ 'TEMP',
2785
+ 'TMP',
2786
+ 'LANG',
2787
+ 'LC_ALL',
2788
+ 'PYTHONPATH',
2789
+ 'NODE_PATH',
2790
+ 'ANTHROPIC_API_KEY',
2791
+ 'OPENAI_API_KEY',
2792
+ 'GEMINI_API_KEY',
2793
+ 'GOOGLE_API_KEY',
2794
+ ]);
2795
+ function runnerSubprocessEnv(inputPath, outputPath) {
2796
+ const env = {};
2797
+ for (const key of RUNNER_ENV_ALLOWLIST) {
2798
+ const value = process.env[key];
2799
+ if (value !== undefined) {
2800
+ env[key] = value;
2801
+ }
2802
+ }
2803
+ env.ORIZU_RUNNER_INPUT_PATH = inputPath;
2804
+ env.ORIZU_RUNNER_OUTPUT_PATH = outputPath;
2805
+ return env;
2806
+ }
2807
+ function boundedRunnerOutput(value) {
2808
+ if (!value)
2809
+ return '';
2810
+ const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value);
2811
+ if (buffer.byteLength <= RUNNER_OUTPUT_MAX_BYTES) {
2812
+ return buffer.toString('utf8');
2813
+ }
2814
+ return `${buffer.subarray(0, RUNNER_OUTPUT_MAX_BYTES).toString('utf8')}\n[truncated]`;
2815
+ }
2667
2816
  async function materializeRunnerVersion(runnerVersionId) {
2668
2817
  const response = await authedFetch(`/api/cli/runner-versions/${encodeURIComponent(runnerVersionId)}/download`);
2669
2818
  if (!response.ok) {
@@ -2672,7 +2821,12 @@ async function materializeRunnerVersion(runnerVersionId) {
2672
2821
  const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-version-'));
2673
2822
  const zipPath = join(tempDir, 'runner.zip');
2674
2823
  const runnerDir = join(tempDir, 'runner');
2675
- writeFileSync(zipPath, new Uint8Array(await response.arrayBuffer()));
2824
+ const zipBytes = new Uint8Array(await response.arrayBuffer());
2825
+ if (zipBytes.byteLength > RUNNER_ARTIFACT_MAX_BYTES) {
2826
+ rmSync(tempDir, { recursive: true, force: true });
2827
+ throw new Error(`Runner artifact exceeds ${RUNNER_ARTIFACT_MAX_BYTES} bytes`);
2828
+ }
2829
+ writeFileSync(zipPath, zipBytes);
2676
2830
  const result = spawnSync('unzip', ['-q', zipPath, '-d', runnerDir], {
2677
2831
  encoding: 'utf8',
2678
2832
  });
@@ -2778,18 +2932,16 @@ async function runnersExec() {
2778
2932
  }));
2779
2933
  const result = spawnSync(manifest.command[0], manifest.command.slice(1), {
2780
2934
  cwd: runnerDir,
2781
- env: {
2782
- ...process.env,
2783
- ORIZU_RUNNER_INPUT_PATH: inputPath,
2784
- ORIZU_RUNNER_OUTPUT_PATH: outputPath,
2785
- },
2935
+ env: runnerSubprocessEnv(inputPath, outputPath),
2786
2936
  encoding: 'utf8',
2937
+ maxBuffer: RUNNER_OUTPUT_MAX_BYTES,
2938
+ timeout: RUNNER_TIMEOUT_MS,
2787
2939
  });
2788
2940
  if (result.error) {
2789
2941
  throw result.error;
2790
2942
  }
2791
2943
  if (result.status !== 0) {
2792
- throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(result.stderr || result.stdout || '')}`);
2944
+ throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(boundedRunnerOutput(result.stderr || result.stdout))}`);
2793
2945
  }
2794
2946
  const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8'));
2795
2947
  resultLines.push(JSON.stringify({
@@ -2890,6 +3042,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
2890
3042
  await listPrompts();
2891
3043
  return;
2892
3044
  }
3045
+ if (command === 'prompts' && subcommand === 'comments') {
3046
+ await listPromptComments();
3047
+ return;
3048
+ }
2893
3049
  if (command === 'prompts' && subcommand === 'pull') {
2894
3050
  await pullPromptArtifact('prompt');
2895
3051
  return;
@@ -2966,6 +3122,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
2966
3122
  await runGepaOptimization();
2967
3123
  return;
2968
3124
  }
3125
+ if (command === 'optimizations' && subcommand === 'export') {
3126
+ await exportOptimizationRun();
3127
+ return;
3128
+ }
2969
3129
  if (command === 'optimizations' &&
2970
3130
  (subcommand === 'pause' ||
2971
3131
  subcommand === 'resume' ||
package/package.json CHANGED
@@ -1,12 +1,22 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "orizu": "dist/index.js"
8
8
  },
9
+ "files": [
10
+ "README.md",
11
+ "dist",
12
+ "scripts",
13
+ "src",
14
+ "tsconfig.json",
15
+ "vendor"
16
+ ],
9
17
  "scripts": {
10
- "build": "tsc -p tsconfig.json"
18
+ "build": "tsc -p tsconfig.json",
19
+ "prepack": "node scripts/vendor-gepa-python.mjs",
20
+ "postpack": "node scripts/vendor-gepa-python.mjs --clean"
11
21
  }
12
22
  }
@@ -0,0 +1,54 @@
1
+ import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
6
+ const cliRoot = resolve(scriptDir, '..')
7
+ const repoRoot = resolve(cliRoot, '..', '..')
8
+ const sourceRoot = resolve(repoRoot, 'packages', 'orizu-gepa-python')
9
+ const vendorRoot = resolve(cliRoot, 'vendor', 'orizu-gepa-python')
10
+ const shouldClean = process.argv.includes('--clean')
11
+ const sourceSrc = resolve(sourceRoot, 'src')
12
+ const sourcePyproject = resolve(sourceRoot, 'pyproject.toml')
13
+ const sourceManifest = resolve(sourceRoot, 'manifest.json')
14
+ const vendoredSrc = resolve(vendorRoot, 'src')
15
+ const vendoredPyproject = resolve(vendorRoot, 'pyproject.toml')
16
+ const vendoredManifest = resolve(vendorRoot, 'manifest.json')
17
+ const hasSourcePackage = existsSync(sourceSrc)
18
+ && existsSync(sourcePyproject)
19
+ && existsSync(sourceManifest)
20
+ const hasVendoredPackage = existsSync(vendoredSrc)
21
+ && existsSync(vendoredPyproject)
22
+ && existsSync(vendoredManifest)
23
+
24
+ if (shouldClean) {
25
+ if (hasSourcePackage) {
26
+ rmSync(vendorRoot, { recursive: true, force: true })
27
+ }
28
+ process.exit(0)
29
+ }
30
+
31
+ if (!hasSourcePackage) {
32
+ if (hasVendoredPackage) {
33
+ process.exit(0)
34
+ }
35
+ throw new Error(`Unable to vendor orizu-gepa-python: source package not found at ${sourceRoot}`)
36
+ }
37
+
38
+ rmSync(vendorRoot, { recursive: true, force: true })
39
+ mkdirSync(vendorRoot, { recursive: true })
40
+ cpSync(sourcePyproject, resolve(vendorRoot, 'pyproject.toml'))
41
+ cpSync(sourceManifest, resolve(vendorRoot, 'manifest.json'))
42
+ cpSync(sourceSrc, resolve(vendorRoot, 'src'), {
43
+ recursive: true,
44
+ filter: sourcePath => {
45
+ const normalizedPath = sourcePath.replace(/\\/g, '/')
46
+ return !normalizedPath.endsWith('/__pycache__')
47
+ && !normalizedPath.includes('/__pycache__/')
48
+ && !normalizedPath.endsWith('.pyc')
49
+ && !normalizedPath.endsWith('.pyo')
50
+ && !normalizedPath.endsWith('/.pytest_cache')
51
+ && !normalizedPath.includes('/.pytest_cache/')
52
+ && !normalizedPath.endsWith('/.DS_Store')
53
+ },
54
+ })