orizu 0.3.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,5 +18,14 @@
18
18
  "build": "tsc -p tsconfig.json",
19
19
  "prepack": "node scripts/vendor-gepa-python.mjs",
20
20
  "postpack": "node scripts/vendor-gepa-python.mjs --clean"
21
+ },
22
+ "dependencies": {
23
+ "@playwright/test": "^1.60.0",
24
+ "@tailwindcss/postcss": "^4.1.7",
25
+ "esbuild": "^0.25.8",
26
+ "postcss": "^8.5.6",
27
+ "react": "19.2.6",
28
+ "react-dom": "19.2.6",
29
+ "tailwindcss": "^4.1.10"
21
30
  }
22
31
  }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ import { parseDatasetFile } from './file-parser.js'
24
24
  import { streamJsonlRowChunks } from './jsonl-stream.js'
25
25
  import { parseDatasetReference } from './dataset-download.js'
26
26
  import { parseGlobalFlags } from './global-flags.js'
27
+ import { runLocalAppPreview } from './preview-runtime.js'
27
28
  import {
28
29
  assertSecureTokenTransport,
29
30
  authedFetch,
@@ -163,6 +164,12 @@ interface ScorerSummary {
163
164
  implementationKind: string
164
165
  }
165
166
 
167
+ interface ScoreSubmitInput {
168
+ resultsJsonl?: string
169
+ aggregate?: Record<string, unknown>
170
+ aggregateWarning?: string
171
+ }
172
+
166
173
  interface RunnerExecContext {
167
174
  prompt: {
168
175
  body: string | null
@@ -263,7 +270,12 @@ function printVersion() {
263
270
  }
264
271
 
265
272
  function printUsage() {
266
- 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>]`)
273
+ 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>]`)
274
+ printPreviewUsage()
275
+ }
276
+
277
+ function printPreviewUsage() {
278
+ 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>]`)
267
279
  }
268
280
 
269
281
  function printOptimizationUsage() {
@@ -2001,18 +2013,23 @@ async function submitScoreResults() {
2001
2013
  const candidateId = getArg('--candidate')
2002
2014
 
2003
2015
  if (!resultsPath || !scorerVersionId || (!subjectPromptVersionId && (!optimizationRunId || !candidateId))) {
2004
- 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]')
2016
+ 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]')
2005
2017
  }
2006
2018
 
2007
2019
  const resultBytes = readSourceBytes(resultsPath)
2008
2020
  const raw = resultsPath.endsWith('.gz')
2009
2021
  ? gunzipSync(resultBytes).toString('utf8')
2010
2022
  : resultBytes.toString('utf8')
2011
- const resultsJsonl = normalizeScoreResultsInput(resultsPath, raw)
2023
+ const scoreInput = normalizeScoreResultsInput(resultsPath, raw, hasArg('--aggregate'))
2024
+ if (scoreInput.aggregateWarning) {
2025
+ console.warn(scoreInput.aggregateWarning)
2026
+ }
2027
+ const aggregatePayload = scoreInput.aggregate || {}
2012
2028
  const response = await authedFetch(`/api/cli/scores/submit?project=${encodeURIComponent(project)}`, {
2013
2029
  method: 'POST',
2014
2030
  headers: { 'Content-Type': 'application/json' },
2015
2031
  body: JSON.stringify({
2032
+ ...aggregatePayload,
2016
2033
  scorerVersionId,
2017
2034
  subjectPromptVersionId: subjectPromptVersionId || undefined,
2018
2035
  datasetVersionId: getArg('--dataset-version') || undefined,
@@ -2020,7 +2037,7 @@ async function submitScoreResults() {
2020
2037
  splitName: getArg('--split') || undefined,
2021
2038
  optimizationRunId: optimizationRunId || undefined,
2022
2039
  candidateId: candidateId || undefined,
2023
- resultsJsonl,
2040
+ resultsJsonl: scoreInput.resultsJsonl,
2024
2041
  }),
2025
2042
  })
2026
2043
 
@@ -2046,23 +2063,159 @@ async function submitScoreResults() {
2046
2063
  )
2047
2064
  }
2048
2065
 
2049
- function normalizeScoreResultsInput(sourcePath: string, raw: string): string {
2066
+ function looksLikeAggregateScoreObject(value: Record<string, unknown>): boolean {
2067
+ return (
2068
+ value.scoreValue !== undefined ||
2069
+ value.score_value !== undefined ||
2070
+ value.diagnostics !== undefined ||
2071
+ value.feedbackSummary !== undefined ||
2072
+ value.feedback_summary !== undefined ||
2073
+ value.rowEvidence !== undefined ||
2074
+ value.row_evidence !== undefined ||
2075
+ value.dependencyScoreRunIds !== undefined ||
2076
+ value.dependency_score_run_ids !== undefined
2077
+ )
2078
+ }
2079
+
2080
+ function numberFromUnknown(value: unknown): number | null {
2081
+ if (typeof value === 'number' && Number.isFinite(value)) return value
2082
+ if (typeof value === 'string' && value.trim()) {
2083
+ const parsed = Number(value)
2084
+ return Number.isFinite(parsed) ? parsed : null
2085
+ }
2086
+ if (typeof value === 'boolean') return value ? 1 : 0
2087
+ return null
2088
+ }
2089
+
2090
+ function normalizeScoreResultsInput(sourcePath: string, raw: string, aggregateMode = false): ScoreSubmitInput {
2050
2091
  const logicalPath = sourcePath.endsWith('.gz') ? sourcePath.slice(0, -3) : sourcePath
2051
- if (!logicalPath.endsWith('.json')) return raw
2092
+ if (!logicalPath.endsWith('.json')) return { resultsJsonl: raw }
2052
2093
 
2053
2094
  try {
2054
2095
  const parsed = JSON.parse(raw) as unknown
2096
+ if (aggregateMode) {
2097
+ if (!isRecord(parsed)) {
2098
+ throw new Error('Aggregate score results JSON must be an object')
2099
+ }
2100
+ return { aggregate: parsed }
2101
+ }
2102
+ if (isRecord(parsed) && looksLikeAggregateScoreObject(parsed)) {
2103
+ return {
2104
+ aggregate: parsed,
2105
+ aggregateWarning: 'Warning: aggregate score JSON detected. Pass --aggregate to make this explicit.',
2106
+ }
2107
+ }
2055
2108
  const rows = Array.isArray(parsed) ? parsed : [parsed]
2056
2109
  if (rows.some(row => !isRecord(row))) {
2057
2110
  throw new Error('JSON score results must be an object or an array of objects')
2058
2111
  }
2059
- return rows.map(row => JSON.stringify(row)).join('\n')
2112
+ return { resultsJsonl: rows.map(row => JSON.stringify(row)).join('\n') }
2060
2113
  } catch (error) {
2061
2114
  const message = error instanceof Error ? error.message : String(error)
2062
2115
  throw new Error(`Invalid score results JSON '${sourcePath}': ${message}`)
2063
2116
  }
2064
2117
  }
2065
2118
 
2119
+ function readOptionalJsonlFile(pathArg: string | null): string | undefined {
2120
+ if (!pathArg) return undefined
2121
+ const bytes = readSourceBytes(pathArg)
2122
+ return pathArg.endsWith('.gz')
2123
+ ? gunzipSync(bytes).toString('utf8')
2124
+ : bytes.toString('utf8')
2125
+ }
2126
+
2127
+ function parseAliasAssignments(value: string | null): Record<string, string> {
2128
+ const result: Record<string, string> = {}
2129
+ if (!value) return result
2130
+ for (const item of parseCommaSeparated(value)) {
2131
+ const equalsIndex = item.indexOf('=')
2132
+ if (equalsIndex <= 0 || equalsIndex === item.length - 1) {
2133
+ throw new Error(`Expected alias assignment in form alias=value, got '${item}'`)
2134
+ }
2135
+ result[item.slice(0, equalsIndex)] = item.slice(equalsIndex + 1)
2136
+ }
2137
+ return result
2138
+ }
2139
+
2140
+ function readDependencyResultAssignments(value: string | null): Record<string, string> | undefined {
2141
+ const assignments = parseAliasAssignments(value)
2142
+ const result: Record<string, string> = {}
2143
+ for (const [alias, path] of Object.entries(assignments)) {
2144
+ result[alias] = readOptionalJsonlFile(path) || ''
2145
+ }
2146
+ return Object.keys(result).length > 0 ? result : undefined
2147
+ }
2148
+
2149
+ async function execScorer() {
2150
+ const project = getArg('--project') || await resolveProjectSlug(null)
2151
+ const scorerVersionId = getArg('--scorer-version')
2152
+ const subjectPromptVersionId = getArg('--subject-version') || getArg('--prompt-version')
2153
+ const optimizationRunId = getArg('--optimization-run')
2154
+ const candidateId = getArg('--candidate')
2155
+ const datasetVersionId = getArg('--dataset-version')
2156
+ const splitSetId = getArg('--split-set')
2157
+ const splitName = getArg('--split')
2158
+ const outPath = getArg('--out')
2159
+
2160
+ if (
2161
+ !scorerVersionId ||
2162
+ (!subjectPromptVersionId && (!optimizationRunId || !candidateId)) ||
2163
+ !datasetVersionId ||
2164
+ !splitSetId ||
2165
+ !splitName
2166
+ ) {
2167
+ 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]')
2168
+ }
2169
+
2170
+ const response = await authedFetch(`/api/cli/scorers/exec?project=${encodeURIComponent(project)}`, {
2171
+ method: 'POST',
2172
+ headers: { 'Content-Type': 'application/json' },
2173
+ body: JSON.stringify({
2174
+ scorerVersionId,
2175
+ subjectPromptVersionId: subjectPromptVersionId || undefined,
2176
+ optimizationRunId: optimizationRunId || undefined,
2177
+ candidateId: candidateId || undefined,
2178
+ datasetVersionId,
2179
+ splitSetId,
2180
+ splitName,
2181
+ submit: !hasArg('--no-submit'),
2182
+ reuseExisting: !hasArg('--no-reuse-existing'),
2183
+ subjectResultsJsonl: readOptionalJsonlFile(getArg('--subject-results') || getArg('--outputs')),
2184
+ dependencyScoreRunIds: parseAliasAssignments(getArg('--dependency-score-run') || getArg('--dependency-score-runs')),
2185
+ dependencyResultsJsonl: readDependencyResultAssignments(getArg('--dependency-results')),
2186
+ }),
2187
+ })
2188
+
2189
+ if (!response.ok) {
2190
+ throw new Error(`Failed to execute scorer: ${await response.text()}`)
2191
+ }
2192
+
2193
+ const data = await parseJsonResponse<Record<string, unknown>>(response, 'Scorer exec')
2194
+ if (outPath) {
2195
+ writeFileSync(expandHomePath(outPath), `${JSON.stringify(data.scoreResult || data, null, 2)}\n`)
2196
+ }
2197
+ if (hasJsonFlag()) {
2198
+ printJson(data)
2199
+ return
2200
+ }
2201
+
2202
+ const scoreRun = isRecord(data.scoreRun) ? data.scoreRun : null
2203
+ const scoreResult = isRecord(data.scoreResult) ? data.scoreResult : null
2204
+ const scoreValue = scoreRun
2205
+ ? scoreRun.scoreValue
2206
+ : scoreResult
2207
+ ? scoreResult.scoreValue
2208
+ : null
2209
+ if (scoreRun && typeof scoreRun.id === 'string') {
2210
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} -> score ${sanitizeTerminalText(scoreRun.id)} (${formatPercent(numberFromUnknown(scoreValue))})`)
2211
+ } else {
2212
+ printLine(`Executed scorer ${sanitizeTerminalText(scorerVersionId)} (${formatPercent(numberFromUnknown(scoreValue))})`)
2213
+ }
2214
+ if (outPath) {
2215
+ printLine(`Wrote scorer result to ${sanitizeTerminalText(outPath)}`)
2216
+ }
2217
+ }
2218
+
2066
2219
  type OptimizationLifecycleAction = 'pause' | 'resume' | 'finish' | 'fail' | 'cancel'
2067
2220
  const OPTIMIZATION_REPORT_MAX_BYTES = 2 * 1024 * 1024
2068
2221
 
@@ -2789,15 +2942,61 @@ async function createAppFromFile() {
2789
2942
  }
2790
2943
 
2791
2944
  const data = await parseJsonResponse<{
2792
- app: { id: string; name: string; versionNum: number; componentName?: string }
2945
+ app: { id: string; name: string; versionNum: number; componentName?: string; url?: string }
2793
2946
  warnings?: string[]
2794
2947
  }>(response, 'App create')
2795
2948
  printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`)
2949
+ if (data.app.url) {
2950
+ printLine(`View app: ${formatTerminalLink(data.app.url)}`)
2951
+ }
2796
2952
  if (data.warnings?.length) {
2797
2953
  printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`)
2798
2954
  }
2799
2955
  }
2800
2956
 
2957
+ async function previewAppFromFile() {
2958
+ const filePath = getArg('--file')
2959
+ const inputSchemaPath = getArg('--input-schema')
2960
+ const outputSchemaPath = getArg('--output-schema')
2961
+ const sampleRowPath = getArg('--sample-row')
2962
+ const screenshotPath = getArg('--screenshot')
2963
+ const component = getArg('--component') || undefined
2964
+ const headed = hasArg('--headed')
2965
+ const keepOpen = hasArg('--keep-open')
2966
+
2967
+ if (!filePath || !inputSchemaPath || !outputSchemaPath || !sampleRowPath) {
2968
+ 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>]')
2969
+ }
2970
+
2971
+ const expandedFilePath = realpathSync(expandHomePath(filePath))
2972
+ const inputJsonSchema = readJsonFile(inputSchemaPath)
2973
+ const outputJsonSchema = readJsonFile(outputSchemaPath)
2974
+ const sampleRow = readJsonFile(sampleRowPath)
2975
+ const expandedScreenshotPath = screenshotPath ? expandHomePath(screenshotPath) : undefined
2976
+
2977
+ const result = await runLocalAppPreview({
2978
+ filePath: expandedFilePath,
2979
+ inputSchema: inputJsonSchema,
2980
+ outputSchema: outputJsonSchema,
2981
+ sampleRow,
2982
+ screenshotPath: expandedScreenshotPath,
2983
+ componentName: component,
2984
+ headed,
2985
+ keepOpen,
2986
+ })
2987
+
2988
+ printLine(`Preview rendered: ${formatTerminalLink(result.url)}`)
2989
+ if (result.screenshotPath) {
2990
+ printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`)
2991
+ }
2992
+ if (headed || keepOpen) {
2993
+ printLine('Headed preview is running. Close Chromium or stop the command when you are done.')
2994
+ }
2995
+ if (result.warnings.length > 0) {
2996
+ printLine(`Warnings: ${sanitizeTerminalText(result.warnings.join('; '))}`)
2997
+ }
2998
+ }
2999
+
2801
3000
  async function updateAppFromFile() {
2802
3001
  const filePath = getArg('--file')
2803
3002
  const inputSchemaPath = getArg('--input-schema')
@@ -4388,6 +4587,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
4388
4587
  return
4389
4588
  }
4390
4589
 
4590
+ if (command === 'scorers' && subcommand === 'exec') {
4591
+ await execScorer()
4592
+ return
4593
+ }
4594
+
4391
4595
  if (command === 'runners' && subcommand === 'push') {
4392
4596
  await pushRunnerArtifact('runner')
4393
4597
  return
@@ -4452,6 +4656,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
4452
4656
  return
4453
4657
  }
4454
4658
 
4659
+ if (command === 'apps' && subcommand === 'preview') {
4660
+ await previewAppFromFile()
4661
+ return
4662
+ }
4663
+
4455
4664
  if (command === 'apps' && subcommand === 'update') {
4456
4665
  await updateAppFromFile()
4457
4666
  return