orizu 0.2.1 → 0.3.0
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 +1404 -11
- package/package.json +1 -1
- package/src/index.ts +1834 -20
package/src/index.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createHash, randomBytes } from 'crypto'
|
|
3
|
-
import { basename, extname } from 'path'
|
|
2
|
+
import { createHash, randomBytes, randomUUID } from 'crypto'
|
|
3
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize } from 'path'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import { createServer } from 'http'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { gunzipSync, gzipSync } from 'zlib'
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
realpathSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
statSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
} from 'fs'
|
|
18
|
+
import { spawn, spawnSync } from 'child_process'
|
|
19
|
+
import { tmpdir } from 'os'
|
|
8
20
|
import { createInterface } from 'readline/promises'
|
|
9
21
|
import { stdin as input, stdout as output } from 'process'
|
|
10
22
|
import { clearServerCredentials, getServerCredentials, saveServerCredentials } from './credentials.js'
|
|
@@ -80,6 +92,41 @@ interface DatasetSelection {
|
|
|
80
92
|
name?: string
|
|
81
93
|
}
|
|
82
94
|
|
|
95
|
+
interface PromptSummary {
|
|
96
|
+
id: string
|
|
97
|
+
name: string
|
|
98
|
+
role: string
|
|
99
|
+
description?: string | null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface ScorerSummary {
|
|
103
|
+
id: string
|
|
104
|
+
name: string
|
|
105
|
+
mode: string
|
|
106
|
+
metricLabel: string
|
|
107
|
+
implementationKind: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface RunnerExecContext {
|
|
111
|
+
prompt: {
|
|
112
|
+
body: string | null
|
|
113
|
+
bodyKind: string
|
|
114
|
+
providerSettings: Record<string, unknown>
|
|
115
|
+
promptId?: string
|
|
116
|
+
promptVersionId: string
|
|
117
|
+
runnerVersionId: string
|
|
118
|
+
}
|
|
119
|
+
scorer?: {
|
|
120
|
+
versionId: string
|
|
121
|
+
metricKey: string
|
|
122
|
+
higherIsBetter: boolean
|
|
123
|
+
} | null
|
|
124
|
+
rows: Array<{
|
|
125
|
+
id: string
|
|
126
|
+
row: Record<string, unknown>
|
|
127
|
+
}>
|
|
128
|
+
}
|
|
129
|
+
|
|
83
130
|
type DatasetUploadSourceType = 'csv' | 'json' | 'jsonl'
|
|
84
131
|
|
|
85
132
|
interface DatasetUploadResponse {
|
|
@@ -153,7 +200,11 @@ function printVersion() {
|
|
|
153
200
|
}
|
|
154
201
|
|
|
155
202
|
function printUsage() {
|
|
156
|
-
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\n orizu logout\n orizu whoami\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 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 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>]`)
|
|
203
|
+
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>]`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function printOptimizationUsage() {
|
|
207
|
+
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]`)
|
|
157
208
|
}
|
|
158
209
|
|
|
159
210
|
let cliArgs = process.argv.slice(2)
|
|
@@ -288,6 +339,92 @@ export async function parseJsonResponse<T>(response: Response, context: string):
|
|
|
288
339
|
)
|
|
289
340
|
}
|
|
290
341
|
}
|
|
342
|
+
|
|
343
|
+
function shellQuote(value: string): string {
|
|
344
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function getStoredAuthTokenForBaseUrl(baseUrl: string): string {
|
|
348
|
+
const credentials = getServerCredentials(baseUrl)
|
|
349
|
+
if (!credentials) {
|
|
350
|
+
throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return 'accessToken' in credentials ? credentials.accessToken : credentials.apiKey
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function printPromptSummaries(items: PromptSummary[], emptyMessage: string) {
|
|
357
|
+
if (items.length === 0) {
|
|
358
|
+
printLine(emptyMessage)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const rows = items.map(item => ({
|
|
363
|
+
id: sanitizeTerminalText(item.id),
|
|
364
|
+
name: sanitizeTerminalText(item.name),
|
|
365
|
+
role: sanitizeTerminalText(item.role),
|
|
366
|
+
}))
|
|
367
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
|
|
368
|
+
const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length))
|
|
369
|
+
const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length))
|
|
370
|
+
|
|
371
|
+
printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)}`)
|
|
372
|
+
printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)}`)
|
|
373
|
+
rows.forEach(row => {
|
|
374
|
+
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)}`)
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function printScorerSummaries(items: ScorerSummary[]) {
|
|
379
|
+
if (items.length === 0) {
|
|
380
|
+
printLine('No scorers found.')
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const rows = items.map(item => ({
|
|
385
|
+
id: sanitizeTerminalText(item.id),
|
|
386
|
+
name: sanitizeTerminalText(item.name),
|
|
387
|
+
mode: sanitizeTerminalText(item.mode),
|
|
388
|
+
metric: sanitizeTerminalText(item.metricLabel),
|
|
389
|
+
implementation: sanitizeTerminalText(item.implementationKind),
|
|
390
|
+
}))
|
|
391
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
|
|
392
|
+
const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length))
|
|
393
|
+
const modeWidth = Math.max('MODE'.length, ...rows.map(row => row.mode.length))
|
|
394
|
+
const metricWidth = Math.max('METRIC'.length, ...rows.map(row => row.metric.length))
|
|
395
|
+
|
|
396
|
+
printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'MODE'.padEnd(modeWidth)} ${'METRIC'.padEnd(metricWidth)} IMPLEMENTATION`)
|
|
397
|
+
printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(modeWidth)} ${'-'.repeat(metricWidth)} ${'-'.repeat('IMPLEMENTATION'.length)}`)
|
|
398
|
+
rows.forEach(row => {
|
|
399
|
+
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.mode.padEnd(modeWidth)} ${row.metric.padEnd(metricWidth)} ${row.implementation}`)
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function readJsonObjectArg(valueArg: string | null, label: string): Record<string, unknown> {
|
|
404
|
+
if (!valueArg) {
|
|
405
|
+
return {}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const trimmed = valueArg.trim()
|
|
409
|
+
const raw = trimmed.startsWith('{')
|
|
410
|
+
? trimmed
|
|
411
|
+
: readSourceFile(trimmed.startsWith('@') ? trimmed.slice(1) : trimmed)
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const parsed = JSON.parse(raw) as unknown
|
|
415
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
416
|
+
throw new Error('JSON root must be an object')
|
|
417
|
+
}
|
|
418
|
+
return parsed as Record<string, unknown>
|
|
419
|
+
} catch (error: any) {
|
|
420
|
+
throw new Error(`Invalid ${label} '${valueArg}': ${error?.message || String(error)}`)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function readJsonPayloadArg(pathArg: string | null): Record<string, unknown> {
|
|
425
|
+
return readJsonObjectArg(pathArg, 'event payload')
|
|
426
|
+
}
|
|
427
|
+
|
|
291
428
|
async function promptSelect<T>(
|
|
292
429
|
title: string,
|
|
293
430
|
items: T[],
|
|
@@ -392,7 +529,8 @@ async function fetchTeamMembers(teamSlug: string): Promise<TeamMember[]> {
|
|
|
392
529
|
return data.members
|
|
393
530
|
}
|
|
394
531
|
|
|
395
|
-
async function
|
|
532
|
+
async function resolveProjectSelection(projectArg: string | null): Promise<Project> {
|
|
533
|
+
projectArg = projectArg || process.env.ORIZU_PROJECT || null
|
|
396
534
|
const teams = await fetchTeams()
|
|
397
535
|
|
|
398
536
|
if (teams.length === 0) {
|
|
@@ -415,7 +553,7 @@ async function resolveProjectSlug(projectArg: string | null): Promise<string> {
|
|
|
415
553
|
{ forcePrompt: true }
|
|
416
554
|
)
|
|
417
555
|
|
|
418
|
-
return
|
|
556
|
+
return project
|
|
419
557
|
}
|
|
420
558
|
|
|
421
559
|
const segments = projectArg.split('/')
|
|
@@ -440,7 +578,7 @@ async function resolveProjectSlug(projectArg: string | null): Promise<string> {
|
|
|
440
578
|
project => `${project.name} (${project.teamSlug}/${project.slug})`
|
|
441
579
|
)
|
|
442
580
|
|
|
443
|
-
return
|
|
581
|
+
return selectedProject
|
|
444
582
|
}
|
|
445
583
|
|
|
446
584
|
const projects = await fetchProjects(matchedTeam.slug)
|
|
@@ -454,10 +592,15 @@ async function resolveProjectSlug(projectArg: string | null): Promise<string> {
|
|
|
454
592
|
project => `${project.name} (${project.teamSlug}/${project.slug})`
|
|
455
593
|
)
|
|
456
594
|
|
|
457
|
-
return
|
|
595
|
+
return selectedProject
|
|
458
596
|
}
|
|
459
597
|
|
|
460
|
-
return
|
|
598
|
+
return matchedProject
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function resolveProjectSlug(projectArg: string | null): Promise<string> {
|
|
602
|
+
const project = await resolveProjectSelection(projectArg)
|
|
603
|
+
return `${project.teamSlug}/${project.slug}`
|
|
461
604
|
}
|
|
462
605
|
|
|
463
606
|
async function selectTaskIdInteractively(): Promise<string> {
|
|
@@ -820,8 +963,14 @@ function renderCliAuthBrowserPage(status: 'success' | 'error'): string {
|
|
|
820
963
|
}
|
|
821
964
|
|
|
822
965
|
async function login() {
|
|
823
|
-
const baseUrl = resolveLoginBaseUrl()
|
|
966
|
+
const baseUrl = hasArg('--no-prompt-if-logged-in') ? getBaseUrl() : resolveLoginBaseUrl()
|
|
824
967
|
assertSecureTokenTransport(baseUrl)
|
|
968
|
+
|
|
969
|
+
if (hasArg('--no-prompt-if-logged-in') && getServerCredentials(baseUrl)) {
|
|
970
|
+
printLine(`Already logged in to ${sanitizeTerminalText(baseUrl)}.`)
|
|
971
|
+
return
|
|
972
|
+
}
|
|
973
|
+
|
|
825
974
|
const codeVerifier = createCodeVerifier()
|
|
826
975
|
const codeChallenge = createCodeChallenge(codeVerifier)
|
|
827
976
|
const callbackPort = resolveAuthCallbackPort()
|
|
@@ -947,19 +1096,1033 @@ async function logout() {
|
|
|
947
1096
|
: undefined,
|
|
948
1097
|
})
|
|
949
1098
|
|
|
950
|
-
if (!response.ok) {
|
|
951
|
-
remoteLogoutError = sanitizeTerminalText(await response.text()).slice(0, 180)
|
|
1099
|
+
if (!response.ok) {
|
|
1100
|
+
remoteLogoutError = sanitizeTerminalText(await response.text()).slice(0, 180)
|
|
1101
|
+
}
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
1104
|
+
remoteLogoutError = sanitizeTerminalText(message).slice(0, 180)
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
clearServerCredentials(baseUrl)
|
|
1108
|
+
if (remoteLogoutError) {
|
|
1109
|
+
console.warn(`Warning: remote logout failed: ${remoteLogoutError}`)
|
|
1110
|
+
}
|
|
1111
|
+
printLine(`Logged out from ${sanitizeTerminalText(baseUrl)}.`)
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
async function printEnv() {
|
|
1115
|
+
const baseUrl = getBaseUrl()
|
|
1116
|
+
const token = getStoredAuthTokenForBaseUrl(baseUrl)
|
|
1117
|
+
const projectArg = getArg('--project')
|
|
1118
|
+
const project = projectArg ? await resolveProjectSelection(projectArg) : null
|
|
1119
|
+
const projectId = project?.id || getArg('--project-id') || process.env.ORIZU_PROJECT_ID || ''
|
|
1120
|
+
const projectSlug = project ? `${project.teamSlug}/${project.slug}` : process.env.ORIZU_PROJECT || ''
|
|
1121
|
+
|
|
1122
|
+
printLine(`export ORIZU_API_URL=${shellQuote(baseUrl)}`)
|
|
1123
|
+
printLine(`export ORIZU_TOKEN=${shellQuote(token)}`)
|
|
1124
|
+
printLine(`export ORIZU_PROJECT_ID=${shellQuote(projectId)}`)
|
|
1125
|
+
if (projectSlug) {
|
|
1126
|
+
printLine(`export ORIZU_PROJECT=${shellQuote(projectSlug)}`)
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
async function logOptimizationEvent() {
|
|
1131
|
+
const eventType = cliArgs[1]
|
|
1132
|
+
const runId = getArg('--run-id')
|
|
1133
|
+
const sequenceArg = getArg('--sequence')
|
|
1134
|
+
const payload = readJsonPayloadArg(getArg('--payload'))
|
|
1135
|
+
const sequence = sequenceArg ? Number(sequenceArg) : Number.NaN
|
|
1136
|
+
|
|
1137
|
+
if (!eventType || eventType.startsWith('--') || !runId || !Number.isInteger(sequence) || sequence <= 0) {
|
|
1138
|
+
throw new Error('Usage: orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json')
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const response = await authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/events`, {
|
|
1142
|
+
method: 'POST',
|
|
1143
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1144
|
+
body: JSON.stringify({
|
|
1145
|
+
eventId: getArg('--event-id') || randomUUID(),
|
|
1146
|
+
sequence,
|
|
1147
|
+
eventType,
|
|
1148
|
+
eventLayer: getArg('--event-layer') || 'core',
|
|
1149
|
+
optimizerFamily: getArg('--optimizer-family'),
|
|
1150
|
+
iteration: getArg('--iteration') ? Number(getArg('--iteration')) : undefined,
|
|
1151
|
+
candidateId: getArg('--candidate-id'),
|
|
1152
|
+
parentCandidateId: getArg('--parent-candidate-id'),
|
|
1153
|
+
childCandidateId: getArg('--child-candidate-id'),
|
|
1154
|
+
payload,
|
|
1155
|
+
}),
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
if (!response.ok) {
|
|
1159
|
+
throw new Error(`Failed to log event: ${await response.text()}`)
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const data = await parseJsonResponse<{ eventId: string }>(response, 'Optimization event log')
|
|
1163
|
+
printLine(`Logged event ${sanitizeTerminalText(data.eventId)}`)
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async function listPrompts() {
|
|
1167
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1168
|
+
const response = await authedFetch(`/api/cli/prompts?project=${encodeURIComponent(project)}`)
|
|
1169
|
+
if (!response.ok) {
|
|
1170
|
+
throw new Error(`Failed to fetch prompts: ${await response.text()}`)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const data = await parseJsonResponse<{ prompts: PromptSummary[] }>(response, 'Prompts list')
|
|
1174
|
+
printPromptSummaries(data.prompts, 'No prompts found.')
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
async function listJudges() {
|
|
1178
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1179
|
+
const response = await authedFetch(`/api/cli/judges?project=${encodeURIComponent(project)}`)
|
|
1180
|
+
if (!response.ok) {
|
|
1181
|
+
throw new Error(`Failed to fetch judges: ${await response.text()}`)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const data = await parseJsonResponse<{ judges: PromptSummary[] }>(response, 'Judges list')
|
|
1185
|
+
printPromptSummaries(data.judges, 'No judges found.')
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function listScorers() {
|
|
1189
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1190
|
+
const response = await authedFetch(`/api/cli/scorers?project=${encodeURIComponent(project)}`)
|
|
1191
|
+
if (!response.ok) {
|
|
1192
|
+
throw new Error(`Failed to fetch scorers: ${await response.text()}`)
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const data = await parseJsonResponse<{ scorers: ScorerSummary[] }>(response, 'Scorers list')
|
|
1196
|
+
printScorerSummaries(data.scorers)
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async function registerScorer() {
|
|
1200
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1201
|
+
const name = getArg('--name')
|
|
1202
|
+
const manifestPath = getArg('--manifest')
|
|
1203
|
+
|
|
1204
|
+
if (!name || !manifestPath) {
|
|
1205
|
+
throw new Error('Usage: orizu scorers register --project <team/project> --name <name> --manifest <manifest.json> [--prompt-version <id>] [--runner-version <id>] [--label <label>] [--json]')
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const manifest = readJsonFile(manifestPath)
|
|
1209
|
+
const response = await authedFetch(`/api/cli/scorers?project=${encodeURIComponent(project)}`, {
|
|
1210
|
+
method: 'POST',
|
|
1211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1212
|
+
body: JSON.stringify({
|
|
1213
|
+
name,
|
|
1214
|
+
manifest,
|
|
1215
|
+
promptVersionId: getArg('--prompt-version') || undefined,
|
|
1216
|
+
runnerVersionId: getArg('--runner-version') || undefined,
|
|
1217
|
+
label: getArg('--label') || undefined,
|
|
1218
|
+
}),
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
if (!response.ok) {
|
|
1222
|
+
throw new Error(`Failed to register scorer: ${await response.text()}`)
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Scorer register')
|
|
1226
|
+
if (hasJsonFlag()) {
|
|
1227
|
+
printJson(data)
|
|
1228
|
+
return
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
printLine(`Registered scorer ${sanitizeTerminalText(name)} (${sanitizeTerminalText(String(data.scorer_version_id || 'unknown version'))})`)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
async function showScorerDetail() {
|
|
1235
|
+
const scorerId = getPositionalArg(2)
|
|
1236
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1237
|
+
if (!scorerId) {
|
|
1238
|
+
throw new Error('Usage: orizu scorers detail <scorer-id-or-name> --project <team/project> [--json]')
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const response = await authedFetch(`/api/cli/scorers/${encodeURIComponent(scorerId)}?project=${encodeURIComponent(project)}`)
|
|
1242
|
+
if (!response.ok) {
|
|
1243
|
+
throw new Error(`Failed to fetch scorer: ${await response.text()}`)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Scorer detail')
|
|
1247
|
+
if (hasJsonFlag()) {
|
|
1248
|
+
printJson(data)
|
|
1249
|
+
return
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const scorer = data.scorer as Record<string, unknown> | undefined
|
|
1253
|
+
printLine(`${sanitizeTerminalText(String(scorer?.name || scorerId))}`)
|
|
1254
|
+
printLine(`Metric: ${sanitizeTerminalText(String(scorer?.metricLabel || 'Score'))}`)
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
async function setScorerLabel() {
|
|
1258
|
+
const scorerName = getPositionalArg(3)
|
|
1259
|
+
const label = getPositionalArg(4)
|
|
1260
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1261
|
+
const scorerVersionId = getArg('--version')
|
|
1262
|
+
|
|
1263
|
+
if (!scorerName || !label || !scorerVersionId) {
|
|
1264
|
+
throw new Error('Usage: orizu scorers labels set <scorer-name> <label> --version <scorer-version-id> [--project <team/project>] [--json]')
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const response = await authedFetch(`/api/cli/scorers/labels?project=${encodeURIComponent(project)}`, {
|
|
1268
|
+
method: 'POST',
|
|
1269
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1270
|
+
body: JSON.stringify({ scorerName, label, scorerVersionId }),
|
|
1271
|
+
})
|
|
1272
|
+
|
|
1273
|
+
if (!response.ok) {
|
|
1274
|
+
throw new Error(`Failed to set scorer label: ${await response.text()}`)
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Scorer label set')
|
|
1278
|
+
if (hasJsonFlag()) {
|
|
1279
|
+
printJson(data)
|
|
1280
|
+
return
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(scorerVersionId)}`)
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async function pushRunnerArtifact(kind: 'runner' | 'optimizer') {
|
|
1287
|
+
const artifactDir = getPositionalArg(2)
|
|
1288
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1289
|
+
const name = getArg('--name')
|
|
1290
|
+
const label = getArg('--label') || undefined
|
|
1291
|
+
|
|
1292
|
+
if (!artifactDir) {
|
|
1293
|
+
throw new Error(`Usage: orizu ${kind === 'runner' ? 'runners' : 'optimizers'} push <dir> --project <team/project> [--name <name>] [--label <label>] [--json]`)
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const manifest = readManifestFile(artifactDir)
|
|
1297
|
+
const artifactName = name || (typeof manifest.name === 'string' ? manifest.name : basename(expandHomePath(artifactDir)))
|
|
1298
|
+
const description = typeof manifest.description === 'string' ? manifest.description : undefined
|
|
1299
|
+
const { zipBase64, contentSha256 } = zipDirectoryToBase64(artifactDir)
|
|
1300
|
+
const endpoint = kind === 'runner' ? 'runners' : 'optimizers'
|
|
1301
|
+
const response = await authedFetch(`/api/cli/${endpoint}?project=${encodeURIComponent(project)}`, {
|
|
1302
|
+
method: 'POST',
|
|
1303
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1304
|
+
body: JSON.stringify({
|
|
1305
|
+
name: artifactName,
|
|
1306
|
+
description,
|
|
1307
|
+
label,
|
|
1308
|
+
manifest,
|
|
1309
|
+
zipBase64,
|
|
1310
|
+
contentSha256,
|
|
1311
|
+
}),
|
|
1312
|
+
})
|
|
1313
|
+
|
|
1314
|
+
if (!response.ok) {
|
|
1315
|
+
throw new Error(`Failed to push ${kind}: ${await response.text()}`)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, `${kind} push`)
|
|
1319
|
+
if (hasJsonFlag()) {
|
|
1320
|
+
printJson(data)
|
|
1321
|
+
return
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const versionId = kind === 'runner' ? data.runner_version_id : data.optimizer_version_id
|
|
1325
|
+
printLine(`Pushed ${kind} ${sanitizeTerminalText(String(artifactName))} (${sanitizeTerminalText(String(versionId || 'unknown version'))})`)
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async function pushPromptArtifact(kind: 'prompt' | 'judge') {
|
|
1329
|
+
const promptDir = getPositionalArg(2)
|
|
1330
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1331
|
+
const runnerVersionArg = getArg('--runner-version')
|
|
1332
|
+
const parentVersionId = getArg('--parent') || undefined
|
|
1333
|
+
|
|
1334
|
+
if (!promptDir) {
|
|
1335
|
+
throw new Error(`Usage: orizu ${kind === 'judge' ? 'judges' : 'prompts'} push <dir> --project <team/project> [--runner-version <id>] [--parent <version-id>] [--json]`)
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const promptRoot = expandHomePath(promptDir)
|
|
1339
|
+
const manifest = readJsonFile(join(promptRoot, 'orizu.prompt.json'))
|
|
1340
|
+
const primaryText = readPromptPrimaryText(manifest, promptRoot)
|
|
1341
|
+
const sidecars = readPromptSidecars(manifest, promptRoot)
|
|
1342
|
+
const runnerFromManifest = stringFromRecord(manifest, 'runner_version_id') ||
|
|
1343
|
+
(isRecord(manifest.runner) ? stringFromRecord(manifest.runner, 'version_id') : undefined)
|
|
1344
|
+
const runnerVersionId = runnerVersionArg || runnerFromManifest
|
|
1345
|
+
if (!runnerVersionId) {
|
|
1346
|
+
throw new Error(`Usage: orizu ${kind === 'judge' ? 'judges' : 'prompts'} push <dir> --project <team/project> [--runner-version <id>] [--parent <version-id>] [--json]`)
|
|
1347
|
+
}
|
|
1348
|
+
const baseBundle = isRecord(manifest.bundle) ? manifest.bundle : {}
|
|
1349
|
+
const endpoint = kind === 'judge' ? 'judges' : 'prompts'
|
|
1350
|
+
const response = await authedFetch(`/api/cli/${endpoint}?project=${encodeURIComponent(project)}`, {
|
|
1351
|
+
method: 'POST',
|
|
1352
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1353
|
+
body: JSON.stringify({
|
|
1354
|
+
name: manifest.name,
|
|
1355
|
+
role: kind === 'judge' && manifest.role === undefined ? 'judge_per_row' : manifest.role,
|
|
1356
|
+
description: manifest.description,
|
|
1357
|
+
body: primaryText.body,
|
|
1358
|
+
bodyKind: primaryText.bodyKind,
|
|
1359
|
+
providerSettings: manifest.provider_settings || {},
|
|
1360
|
+
bundle: {
|
|
1361
|
+
...baseBundle,
|
|
1362
|
+
tags: manifest.tags || [],
|
|
1363
|
+
provenance: manifest.provenance || {},
|
|
1364
|
+
primaryText: {
|
|
1365
|
+
path: primaryText.path,
|
|
1366
|
+
kind: primaryText.bodyKind,
|
|
1367
|
+
},
|
|
1368
|
+
sidecars,
|
|
1369
|
+
},
|
|
1370
|
+
runnerVersionId,
|
|
1371
|
+
parentVersionId,
|
|
1372
|
+
versionLabel: manifest.version_label,
|
|
1373
|
+
createdBy: manifest.provenance || { kind: 'human-edit' },
|
|
1374
|
+
}),
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
if (!response.ok) {
|
|
1378
|
+
throw new Error(`Failed to push ${kind}: ${await response.text()}`)
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, `${kind} push`)
|
|
1382
|
+
if (hasJsonFlag()) {
|
|
1383
|
+
printJson(data)
|
|
1384
|
+
return
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
printLine(`Pushed ${kind} ${sanitizeTerminalText(String(manifest.name || promptDir))} (${sanitizeTerminalText(String(data.prompt_version_id || 'unknown version'))})`)
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
async function pullPromptArtifact(kind: 'prompt' | 'judge') {
|
|
1391
|
+
const promptRef = getPositionalArg(2)
|
|
1392
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1393
|
+
const outDir = getArg('--out')
|
|
1394
|
+
const label = getArg('--label')
|
|
1395
|
+
const version = getArg('--version')
|
|
1396
|
+
|
|
1397
|
+
if (!promptRef || !outDir) {
|
|
1398
|
+
throw new Error(`Usage: orizu ${kind === 'judge' ? 'judges' : 'prompts'} pull <prompt-id-or-name> --project <team/project> --out <dir> [--label <label> | --version <version-id>] [--json]`)
|
|
1399
|
+
}
|
|
1400
|
+
if (label && version) {
|
|
1401
|
+
throw new Error('Use either --label or --version, not both')
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const params = new URLSearchParams({ project })
|
|
1405
|
+
if (label) params.set('label', label)
|
|
1406
|
+
if (version) params.set('version', version)
|
|
1407
|
+
|
|
1408
|
+
const endpoint = kind === 'judge' ? 'judges' : 'prompts'
|
|
1409
|
+
const response = await authedFetch(`/api/cli/${endpoint}/${encodeURIComponent(promptRef)}?${params.toString()}`)
|
|
1410
|
+
if (!response.ok) {
|
|
1411
|
+
throw new Error(`Failed to pull ${kind}: ${await response.text()}`)
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const data = await parseJsonResponse<{
|
|
1415
|
+
prompt: {
|
|
1416
|
+
id: string
|
|
1417
|
+
name: string
|
|
1418
|
+
role: string
|
|
1419
|
+
description?: string | null
|
|
1420
|
+
}
|
|
1421
|
+
version: {
|
|
1422
|
+
id: string
|
|
1423
|
+
versionNumber?: number
|
|
1424
|
+
versionLabel?: string | null
|
|
1425
|
+
body?: string | null
|
|
1426
|
+
bodyKind?: string | null
|
|
1427
|
+
providerSettings?: Record<string, unknown>
|
|
1428
|
+
runnerVersionId?: string | null
|
|
1429
|
+
bundle?: Record<string, unknown>
|
|
1430
|
+
}
|
|
1431
|
+
labels?: Array<{ label: string; promptVersionId: string }>
|
|
1432
|
+
}>(response, `${kind} pull`)
|
|
1433
|
+
|
|
1434
|
+
const targetDir = expandHomePath(outDir)
|
|
1435
|
+
mkdirSync(targetDir, { recursive: true })
|
|
1436
|
+
|
|
1437
|
+
const bundle = isRecord(data.version.bundle) ? data.version.bundle : {}
|
|
1438
|
+
const bundlePrimaryText = isRecord(bundle.primaryText)
|
|
1439
|
+
? bundle.primaryText
|
|
1440
|
+
: isRecord(bundle.primary_text)
|
|
1441
|
+
? bundle.primary_text
|
|
1442
|
+
: null
|
|
1443
|
+
const primaryPath = safeRelativePath(
|
|
1444
|
+
stringFromRecord(bundlePrimaryText || {}, 'path') || 'prompt.md',
|
|
1445
|
+
'prompt.md'
|
|
1446
|
+
)
|
|
1447
|
+
const bodyKind = stringFromRecord(bundlePrimaryText || {}, 'kind') || data.version.bodyKind || 'text'
|
|
1448
|
+
|
|
1449
|
+
writeTextFileEnsuringDir(join(targetDir, primaryPath), data.version.body || '')
|
|
1450
|
+
|
|
1451
|
+
const exportedSidecars: Array<Record<string, unknown>> = []
|
|
1452
|
+
const sidecars = Array.isArray(bundle.sidecars) ? bundle.sidecars : []
|
|
1453
|
+
for (const rawSidecar of sidecars) {
|
|
1454
|
+
if (!isRecord(rawSidecar)) continue
|
|
1455
|
+
const sidecarPathValue = stringFromRecord(rawSidecar, 'path')
|
|
1456
|
+
if (!sidecarPathValue) continue
|
|
1457
|
+
const sidecarPath = safeRelativePath(sidecarPathValue, sidecarPathValue)
|
|
1458
|
+
const content = typeof rawSidecar.content === 'string' ? rawSidecar.content : null
|
|
1459
|
+
const contentSha256 = stringFromRecord(rawSidecar, 'contentSha256') ||
|
|
1460
|
+
stringFromRecord(rawSidecar, 'content_sha256') ||
|
|
1461
|
+
(content !== null ? sha256Hex(content) : undefined)
|
|
1462
|
+
if (content !== null) {
|
|
1463
|
+
writeTextFileEnsuringDir(join(targetDir, sidecarPath), content)
|
|
1464
|
+
}
|
|
1465
|
+
const { content: _ignoredContent, contentSha256: _ignoredCamel, content_sha256: _ignoredSnake, ...metadata } = rawSidecar
|
|
1466
|
+
exportedSidecars.push({
|
|
1467
|
+
...metadata,
|
|
1468
|
+
type: stringFromRecord(rawSidecar, 'type') || 'file',
|
|
1469
|
+
path: sidecarPath,
|
|
1470
|
+
...(contentSha256 ? { content_sha256: contentSha256 } : {}),
|
|
1471
|
+
})
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
const labels = (data.labels || [])
|
|
1475
|
+
.filter(item => item.promptVersionId === data.version.id)
|
|
1476
|
+
.map(item => item.label)
|
|
1477
|
+
|
|
1478
|
+
const manifest: Record<string, unknown> = {
|
|
1479
|
+
schema_version: 'orizu.prompt.v1',
|
|
1480
|
+
name: data.prompt.name,
|
|
1481
|
+
role: data.prompt.role,
|
|
1482
|
+
description: data.prompt.description || undefined,
|
|
1483
|
+
primary_text: {
|
|
1484
|
+
path: primaryPath,
|
|
1485
|
+
kind: bodyKind,
|
|
1486
|
+
},
|
|
1487
|
+
provider_settings: data.version.providerSettings || {},
|
|
1488
|
+
runner_version_id: data.version.runnerVersionId || undefined,
|
|
1489
|
+
version_id: data.version.id,
|
|
1490
|
+
version_number: data.version.versionNumber,
|
|
1491
|
+
version_label: data.version.versionLabel || undefined,
|
|
1492
|
+
labels,
|
|
1493
|
+
sidecars: exportedSidecars,
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
writeTextFileEnsuringDir(
|
|
1497
|
+
join(targetDir, 'orizu.prompt.json'),
|
|
1498
|
+
`${JSON.stringify(manifest, null, 2)}\n`
|
|
1499
|
+
)
|
|
1500
|
+
|
|
1501
|
+
if (hasJsonFlag()) {
|
|
1502
|
+
printJson({
|
|
1503
|
+
prompt_id: data.prompt.id,
|
|
1504
|
+
prompt_version_id: data.version.id,
|
|
1505
|
+
path: targetDir,
|
|
1506
|
+
})
|
|
1507
|
+
return
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
printLine(`Pulled ${kind} ${sanitizeTerminalText(data.prompt.name)} to ${sanitizeTerminalText(targetDir)}`)
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
async function setPromptLabel() {
|
|
1514
|
+
const promptName = getPositionalArg(3)
|
|
1515
|
+
const label = getPositionalArg(4)
|
|
1516
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1517
|
+
const promptVersionId = getArg('--version')
|
|
1518
|
+
|
|
1519
|
+
if (!promptName || !label || !promptVersionId) {
|
|
1520
|
+
throw new Error('Usage: orizu prompts labels set <prompt-name> <label> --version <prompt-version-id> [--project <team/project>] [--json]')
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const response = await authedFetch(`/api/cli/prompts/labels?project=${encodeURIComponent(project)}`, {
|
|
1524
|
+
method: 'POST',
|
|
1525
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1526
|
+
body: JSON.stringify({
|
|
1527
|
+
promptName,
|
|
1528
|
+
label,
|
|
1529
|
+
promptVersionId,
|
|
1530
|
+
}),
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
if (!response.ok) {
|
|
1534
|
+
throw new Error(`Failed to set prompt label: ${await response.text()}`)
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Prompt label set')
|
|
1538
|
+
if (hasJsonFlag()) {
|
|
1539
|
+
printJson(data)
|
|
1540
|
+
return
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`)
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
async function bindPromptScorer(role: 'headline' | 'tracked') {
|
|
1547
|
+
const promptId = getPositionalArg(3)
|
|
1548
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1549
|
+
const scorerVersionId = getArg('--scorer-version')
|
|
1550
|
+
|
|
1551
|
+
if (!promptId || !scorerVersionId) {
|
|
1552
|
+
throw new Error(`Usage: orizu prompts scorers ${role === 'headline' ? 'set-headline' : 'add'} <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]`)
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const response = await authedFetch(`/api/cli/prompts/scorers?project=${encodeURIComponent(project)}`, {
|
|
1556
|
+
method: 'POST',
|
|
1557
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1558
|
+
body: JSON.stringify({
|
|
1559
|
+
promptId,
|
|
1560
|
+
scorerVersionId,
|
|
1561
|
+
role,
|
|
1562
|
+
datasetVersionId: getArg('--dataset-version') || undefined,
|
|
1563
|
+
splitSetId: getArg('--split-set') || undefined,
|
|
1564
|
+
splitName: getArg('--split') || undefined,
|
|
1565
|
+
}),
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
if (!response.ok) {
|
|
1569
|
+
throw new Error(`Failed to bind prompt scorer: ${await response.text()}`)
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Prompt scorer bind')
|
|
1573
|
+
if (hasJsonFlag()) {
|
|
1574
|
+
printJson(data)
|
|
1575
|
+
return
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
printLine(`${role === 'headline' ? 'Set headline' : 'Added'} scorer ${sanitizeTerminalText(scorerVersionId)} for ${sanitizeTerminalText(promptId)}`)
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function getPositionalArg(index: number): string | null {
|
|
1582
|
+
const value = cliArgs[index]
|
|
1583
|
+
return value && !value.startsWith('--') ? value : null
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function parseRatioFlag(name: string, fallback: number): number {
|
|
1587
|
+
const value = getArg(name)
|
|
1588
|
+
if (!value) {
|
|
1589
|
+
return fallback
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
const parsed = Number(value)
|
|
1593
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
1594
|
+
throw new Error(`${name} must be a number between 0 and 1`)
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
return parsed
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function parsePositiveIntegerFlag(name: string, fallback: number): number {
|
|
1601
|
+
const value = getArg(name)
|
|
1602
|
+
if (!value) {
|
|
1603
|
+
return fallback
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const parsed = Number(value)
|
|
1607
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1608
|
+
throw new Error(`${name} must be a positive integer`)
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
return parsed
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function formatPercent(value: number | null): string {
|
|
1615
|
+
if (value === null || !Number.isFinite(value)) {
|
|
1616
|
+
return 'unscored'
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
return `${(value * 100).toFixed(1)}%`
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function looksLikeUuid(value: string): boolean {
|
|
1623
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
async function resolveDatasetIdForProject(datasetRef: string, projectArg: string | null): Promise<string> {
|
|
1627
|
+
if (looksLikeUuid(datasetRef)) {
|
|
1628
|
+
return datasetRef
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
projectArg = projectArg || process.env.ORIZU_PROJECT || null
|
|
1632
|
+
if (!projectArg) {
|
|
1633
|
+
return datasetRef
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
const project = projectArg || await resolveProjectSlug(null)
|
|
1637
|
+
const datasets = await fetchDatasets(project)
|
|
1638
|
+
const matches = datasets.filter(dataset => dataset.name === datasetRef || dataset.id === datasetRef)
|
|
1639
|
+
|
|
1640
|
+
if (matches.length === 0) {
|
|
1641
|
+
throw new Error(`Dataset '${sanitizeTerminalText(datasetRef)}' not found in ${sanitizeTerminalText(project)}`)
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
return matches[0].id
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
async function createDatasetVersion() {
|
|
1648
|
+
const datasetId = getPositionalArg(3) || getArg('--dataset')
|
|
1649
|
+
const versionLabel = getArg('--label') || getArg('--version-label') || null
|
|
1650
|
+
const project = getArg('--project')
|
|
1651
|
+
|
|
1652
|
+
if (!datasetId) {
|
|
1653
|
+
throw new Error('Usage: orizu datasets versions create <datasetId|dataset-name> [--project <team/project>] [--label <label>] [--json]')
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const resolvedDatasetId = await resolveDatasetIdForProject(datasetId, project)
|
|
1657
|
+
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(resolvedDatasetId)}/versions`, {
|
|
1658
|
+
method: 'POST',
|
|
1659
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1660
|
+
body: JSON.stringify({ versionLabel }),
|
|
1661
|
+
})
|
|
1662
|
+
|
|
1663
|
+
if (!response.ok) {
|
|
1664
|
+
throw new Error(`Failed to create dataset version: ${await response.text()}`)
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const data = await parseJsonResponse<{
|
|
1668
|
+
datasetVersion: {
|
|
1669
|
+
id: string
|
|
1670
|
+
rowCount: number
|
|
1671
|
+
artifactFormat?: string
|
|
1672
|
+
artifactStoragePath?: string
|
|
1673
|
+
}
|
|
1674
|
+
}>(response, 'Dataset version create')
|
|
1675
|
+
|
|
1676
|
+
if (hasJsonFlag()) {
|
|
1677
|
+
printJson({
|
|
1678
|
+
dataset_version_id: data.datasetVersion.id,
|
|
1679
|
+
row_count: data.datasetVersion.rowCount,
|
|
1680
|
+
})
|
|
1681
|
+
return
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const details = [
|
|
1685
|
+
`${data.datasetVersion.rowCount} rows`,
|
|
1686
|
+
data.datasetVersion.artifactFormat,
|
|
1687
|
+
].filter(Boolean).join(', ')
|
|
1688
|
+
printLine(
|
|
1689
|
+
`Created dataset version ${sanitizeTerminalText(data.datasetVersion.id)}` +
|
|
1690
|
+
(details ? ` (${sanitizeTerminalText(details)})` : '')
|
|
1691
|
+
)
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
async function createDatasetSplitSet() {
|
|
1695
|
+
const datasetVersionId = getPositionalArg(3) || getArg('--dataset-version')
|
|
1696
|
+
const splitFile = getArg('--from-file')
|
|
1697
|
+
const splitSpec = splitFile ? readJsonFile(splitFile) : null
|
|
1698
|
+
const name = getArg('--name') || (typeof splitSpec?.name === 'string' ? splitSpec.name : 'default')
|
|
1699
|
+
const strategy = getArg('--strategy') || (typeof splitSpec?.strategy === 'string' ? splitSpec.strategy : 'random')
|
|
1700
|
+
const seed = splitSpec && (splitSpec.seed === null || typeof splitSpec.seed === 'number')
|
|
1701
|
+
? splitSpec.seed
|
|
1702
|
+
: parsePositiveIntegerFlag('--seed', 1)
|
|
1703
|
+
const train = parseRatioFlag('--train', 0.7)
|
|
1704
|
+
const validation = parseRatioFlag('--validation', 0.2)
|
|
1705
|
+
const test = parseRatioFlag('--test', 0.1)
|
|
1706
|
+
|
|
1707
|
+
if (!datasetVersionId) {
|
|
1708
|
+
throw new Error(
|
|
1709
|
+
'Usage: orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--name <name>] [--seed <n>] [--train <ratio>] [--validation <ratio>] [--test <ratio>] [--json]'
|
|
1710
|
+
)
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const partitions = splitSpec && Array.isArray(splitSpec.partitions) ? splitSpec.partitions : undefined
|
|
1714
|
+
const metadata = splitSpec && typeof splitSpec.metadata === 'object' && splitSpec.metadata !== null
|
|
1715
|
+
? splitSpec.metadata
|
|
1716
|
+
: undefined
|
|
1717
|
+
|
|
1718
|
+
const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(datasetVersionId)}/split-sets`, {
|
|
1719
|
+
method: 'POST',
|
|
1720
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1721
|
+
body: JSON.stringify({
|
|
1722
|
+
name,
|
|
1723
|
+
strategy,
|
|
1724
|
+
seed,
|
|
1725
|
+
train,
|
|
1726
|
+
validation,
|
|
1727
|
+
test,
|
|
1728
|
+
partitions,
|
|
1729
|
+
metadata,
|
|
1730
|
+
}),
|
|
1731
|
+
})
|
|
1732
|
+
|
|
1733
|
+
if (!response.ok) {
|
|
1734
|
+
throw new Error(`Failed to create dataset split set: ${await response.text()}`)
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const data = await parseJsonResponse<{ splitSet: { id: string } }>(response, 'Dataset split set create')
|
|
1738
|
+
if (hasJsonFlag()) {
|
|
1739
|
+
printJson({ split_set_id: data.splitSet.id })
|
|
1740
|
+
return
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
printLine(`Created split set ${sanitizeTerminalText(data.splitSet.id)}`)
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
async function submitRunResults() {
|
|
1747
|
+
const resultsPath = getPositionalArg(2) || getArg('--file') || getArg('--results')
|
|
1748
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1749
|
+
const promptVersionId = getArg('--prompt-version')
|
|
1750
|
+
const runnerVersionId = getArg('--runner-version')
|
|
1751
|
+
const datasetVersionId = getArg('--dataset-version')
|
|
1752
|
+
const splitSetId = getArg('--split-set')
|
|
1753
|
+
const splitName = getArg('--split')
|
|
1754
|
+
|
|
1755
|
+
if (
|
|
1756
|
+
!resultsPath ||
|
|
1757
|
+
!promptVersionId ||
|
|
1758
|
+
!runnerVersionId ||
|
|
1759
|
+
!datasetVersionId ||
|
|
1760
|
+
!splitSetId ||
|
|
1761
|
+
!splitName
|
|
1762
|
+
) {
|
|
1763
|
+
throw new Error(
|
|
1764
|
+
'Usage: orizu runs submit <results.jsonl> --project <team/project> --prompt-version <id> --runner-version <id> --dataset-version <id> --split-set <id> --split <name> [--judge-version <id>] [--judge-runner-version <id>]'
|
|
1765
|
+
)
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const resultBytes = readSourceBytes(resultsPath)
|
|
1769
|
+
const resultsJsonl = resultsPath.endsWith('.gz')
|
|
1770
|
+
? gunzipSync(resultBytes).toString('utf8')
|
|
1771
|
+
: resultBytes.toString('utf8')
|
|
1772
|
+
const response = await authedFetch(`/api/cli/runs/submit?project=${encodeURIComponent(project)}`, {
|
|
1773
|
+
method: 'POST',
|
|
1774
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1775
|
+
body: JSON.stringify({
|
|
1776
|
+
promptVersionId,
|
|
1777
|
+
runnerVersionId,
|
|
1778
|
+
datasetVersionId,
|
|
1779
|
+
splitSetId,
|
|
1780
|
+
splitName,
|
|
1781
|
+
judgeVersionId: getArg('--judge-version') || undefined,
|
|
1782
|
+
judgeRunnerVersionId: getArg('--judge-runner-version') || undefined,
|
|
1783
|
+
resultsJsonl,
|
|
1784
|
+
}),
|
|
1785
|
+
})
|
|
1786
|
+
|
|
1787
|
+
if (!response.ok) {
|
|
1788
|
+
throw new Error(`Failed to submit run: ${await response.text()}`)
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const data = await parseJsonResponse<{
|
|
1792
|
+
run: {
|
|
1793
|
+
id: string
|
|
1794
|
+
aggregateScore: number | null
|
|
1795
|
+
perRowResultsStoragePath: string
|
|
1796
|
+
}
|
|
1797
|
+
}>(response, 'Run submit')
|
|
1798
|
+
printLine(
|
|
1799
|
+
`Submitted run ${sanitizeTerminalText(data.run.id)} ` +
|
|
1800
|
+
`(${formatPercent(data.run.aggregateScore)}) -> ` +
|
|
1801
|
+
sanitizeTerminalText(data.run.perRowResultsStoragePath)
|
|
1802
|
+
)
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
async function submitScoreResults() {
|
|
1806
|
+
const resultsPath = getPositionalArg(2) || getArg('--file') || getArg('--results')
|
|
1807
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1808
|
+
const scorerVersionId = getArg('--scorer-version')
|
|
1809
|
+
const subjectPromptVersionId = getArg('--subject-version') || getArg('--prompt-version')
|
|
1810
|
+
const optimizationRunId = getArg('--optimization-run')
|
|
1811
|
+
const candidateId = getArg('--candidate')
|
|
1812
|
+
|
|
1813
|
+
if (!resultsPath || !scorerVersionId || (!subjectPromptVersionId && (!optimizationRunId || !candidateId))) {
|
|
1814
|
+
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]')
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const resultBytes = readSourceBytes(resultsPath)
|
|
1818
|
+
const raw = resultsPath.endsWith('.gz')
|
|
1819
|
+
? gunzipSync(resultBytes).toString('utf8')
|
|
1820
|
+
: resultBytes.toString('utf8')
|
|
1821
|
+
const resultsJsonl = normalizeScoreResultsInput(resultsPath, raw)
|
|
1822
|
+
const response = await authedFetch(`/api/cli/scores/submit?project=${encodeURIComponent(project)}`, {
|
|
1823
|
+
method: 'POST',
|
|
1824
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1825
|
+
body: JSON.stringify({
|
|
1826
|
+
scorerVersionId,
|
|
1827
|
+
subjectPromptVersionId: subjectPromptVersionId || undefined,
|
|
1828
|
+
datasetVersionId: getArg('--dataset-version') || undefined,
|
|
1829
|
+
splitSetId: getArg('--split-set') || undefined,
|
|
1830
|
+
splitName: getArg('--split') || undefined,
|
|
1831
|
+
optimizationRunId: optimizationRunId || undefined,
|
|
1832
|
+
candidateId: candidateId || undefined,
|
|
1833
|
+
resultsJsonl,
|
|
1834
|
+
}),
|
|
1835
|
+
})
|
|
1836
|
+
|
|
1837
|
+
if (!response.ok) {
|
|
1838
|
+
throw new Error(`Failed to submit score: ${await response.text()}`)
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
const data = await parseJsonResponse<{
|
|
1842
|
+
scoreRun: {
|
|
1843
|
+
id: string
|
|
1844
|
+
scoreValue: number | null
|
|
1845
|
+
rowResultsStoragePath?: string | null
|
|
1846
|
+
}
|
|
1847
|
+
}>(response, 'Score submit')
|
|
1848
|
+
if (hasJsonFlag()) {
|
|
1849
|
+
printJson(data)
|
|
1850
|
+
return
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
printLine(
|
|
1854
|
+
`Submitted score ${sanitizeTerminalText(data.scoreRun.id)} ` +
|
|
1855
|
+
`(${formatPercent(data.scoreRun.scoreValue)})`
|
|
1856
|
+
)
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function normalizeScoreResultsInput(sourcePath: string, raw: string): string {
|
|
1860
|
+
const logicalPath = sourcePath.endsWith('.gz') ? sourcePath.slice(0, -3) : sourcePath
|
|
1861
|
+
if (!logicalPath.endsWith('.json')) return raw
|
|
1862
|
+
|
|
1863
|
+
try {
|
|
1864
|
+
const parsed = JSON.parse(raw) as unknown
|
|
1865
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed]
|
|
1866
|
+
if (rows.some(row => !isRecord(row))) {
|
|
1867
|
+
throw new Error('JSON score results must be an object or an array of objects')
|
|
1868
|
+
}
|
|
1869
|
+
return rows.map(row => JSON.stringify(row)).join('\n')
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
1872
|
+
throw new Error(`Invalid score results JSON '${sourcePath}': ${message}`)
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
type OptimizationLifecycleAction = 'pause' | 'resume' | 'finish' | 'fail' | 'cancel'
|
|
1877
|
+
|
|
1878
|
+
function parseOptionalNumberFlag(name: string): number | undefined {
|
|
1879
|
+
const value = getArg(name)
|
|
1880
|
+
if (!value) {
|
|
1881
|
+
return undefined
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
const parsed = Number(value)
|
|
1885
|
+
if (!Number.isFinite(parsed)) {
|
|
1886
|
+
throw new Error(`${name} must be a number`)
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
return parsed
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function hasObjectKeys(value: Record<string, unknown>): boolean {
|
|
1893
|
+
return Object.keys(value).length > 0
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
async function startOptimizationRun() {
|
|
1897
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1898
|
+
const optimizerVersionId = getArg('--optimizer-version')
|
|
1899
|
+
const promptVersionIds = parseCommaSeparated(
|
|
1900
|
+
getArg('--prompt-version') || getArg('--prompt-versions')
|
|
1901
|
+
)
|
|
1902
|
+
const judgeVersionIds = parseCommaSeparated(
|
|
1903
|
+
getArg('--judge-version') || getArg('--judge-versions')
|
|
1904
|
+
)
|
|
1905
|
+
const selectionScorer = getArg('--selection-scorer') || getArg('--scorer-version')
|
|
1906
|
+
const reflectionScorer = getArg('--reflection-scorer')
|
|
1907
|
+
const paretoScorers = parseCommaSeparated(getArg('--pareto-scorer') || getArg('--pareto-scorers'))
|
|
1908
|
+
const bestScorers = parseCommaSeparated(getArg('--best-scorer') || getArg('--best-scorers'))
|
|
1909
|
+
const datasetVersionId = getArg('--dataset-version')
|
|
1910
|
+
const splitSetId = getArg('--split-set')
|
|
1911
|
+
const metadata = readJsonObjectArg(getArg('--metadata'), 'optimization metadata')
|
|
1912
|
+
|
|
1913
|
+
if (
|
|
1914
|
+
!optimizerVersionId ||
|
|
1915
|
+
promptVersionIds.length === 0 ||
|
|
1916
|
+
!selectionScorer ||
|
|
1917
|
+
!datasetVersionId ||
|
|
1918
|
+
!splitSetId
|
|
1919
|
+
) {
|
|
1920
|
+
throw new Error(
|
|
1921
|
+
'Usage: 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]'
|
|
1922
|
+
)
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
const scorers = [
|
|
1926
|
+
{ scorerVersionId: selectionScorer, role: 'selection' },
|
|
1927
|
+
...(reflectionScorer ? [{ scorerVersionId: reflectionScorer, role: 'reflection' }] : []),
|
|
1928
|
+
...paretoScorers.map(scorerVersionId => ({
|
|
1929
|
+
scorerVersionId,
|
|
1930
|
+
role: 'tracked',
|
|
1931
|
+
trackedScope: 'pareto_candidates',
|
|
1932
|
+
})),
|
|
1933
|
+
...bestScorers.map(scorerVersionId => ({
|
|
1934
|
+
scorerVersionId,
|
|
1935
|
+
role: 'tracked',
|
|
1936
|
+
trackedScope: 'best_candidate',
|
|
1937
|
+
})),
|
|
1938
|
+
]
|
|
1939
|
+
|
|
1940
|
+
const response = await authedFetch(`/api/cli/optimization-runs?project=${encodeURIComponent(project)}`, {
|
|
1941
|
+
method: 'POST',
|
|
1942
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1943
|
+
body: JSON.stringify({
|
|
1944
|
+
optimizerVersionId,
|
|
1945
|
+
promptVersionIds,
|
|
1946
|
+
judgeVersionIds,
|
|
1947
|
+
scorers,
|
|
1948
|
+
datasetVersionId,
|
|
1949
|
+
splitSetId,
|
|
1950
|
+
trainSplitName: getArg('--train-split') || undefined,
|
|
1951
|
+
validationSplitName: getArg('--validation-split') || undefined,
|
|
1952
|
+
metadata,
|
|
1953
|
+
}),
|
|
1954
|
+
})
|
|
1955
|
+
|
|
1956
|
+
if (!response.ok) {
|
|
1957
|
+
throw new Error(`Failed to start optimization run: ${await response.text()}`)
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
const data = await parseJsonResponse<{
|
|
1961
|
+
optimization_run_id: string
|
|
1962
|
+
optimizationRun: {
|
|
1963
|
+
id: string
|
|
1964
|
+
status: string
|
|
1965
|
+
startedAt?: string | null
|
|
1966
|
+
createdAt?: string | null
|
|
1967
|
+
}
|
|
1968
|
+
}>(response, 'Optimization run start')
|
|
1969
|
+
|
|
1970
|
+
if (hasJsonFlag()) {
|
|
1971
|
+
printJson(data)
|
|
1972
|
+
return
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
printLine(
|
|
1976
|
+
`Started optimization run ${sanitizeTerminalText(data.optimization_run_id)} ` +
|
|
1977
|
+
`(${sanitizeTerminalText(data.optimizationRun.status)})`
|
|
1978
|
+
)
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
async function updateOptimizationRunLifecycle(action: OptimizationLifecycleAction) {
|
|
1982
|
+
const runId = getPositionalArg(2) || getArg('--run-id')
|
|
1983
|
+
if (!runId) {
|
|
1984
|
+
throw new Error(`Usage: orizu optimizations ${action} <run-id>`)
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
const statusByAction: Record<OptimizationLifecycleAction, string> = {
|
|
1988
|
+
pause: 'paused',
|
|
1989
|
+
resume: 'running',
|
|
1990
|
+
finish: 'succeeded',
|
|
1991
|
+
fail: 'failed',
|
|
1992
|
+
cancel: 'cancelled',
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
const body: Record<string, unknown> = {
|
|
1996
|
+
status: statusByAction[action],
|
|
1997
|
+
}
|
|
1998
|
+
const metadata = readJsonObjectArg(getArg('--metadata'), 'optimization metadata')
|
|
1999
|
+
const reason = getArg('--reason')
|
|
2000
|
+
|
|
2001
|
+
if ((action === 'pause' || action === 'cancel') && reason) {
|
|
2002
|
+
metadata.reason = reason
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
if (action === 'fail' && reason) {
|
|
2006
|
+
metadata.failure_reason = reason
|
|
2007
|
+
body.failureReason = reason
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (hasObjectKeys(metadata)) {
|
|
2011
|
+
body.metadata = metadata
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
if (action === 'finish') {
|
|
2015
|
+
const bestScore = parseOptionalNumberFlag('--best-score')
|
|
2016
|
+
if (bestScore !== undefined) {
|
|
2017
|
+
body.bestScore = bestScore
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
const bestCandidateId = getArg('--best-candidate')
|
|
2021
|
+
if (bestCandidateId) {
|
|
2022
|
+
body.bestCandidateId = bestCandidateId
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
const resultPromptVersionId = getArg('--result-prompt-version')
|
|
2026
|
+
if (resultPromptVersionId) {
|
|
2027
|
+
body.resultPromptVersionId = resultPromptVersionId
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const response = await authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}`, {
|
|
2032
|
+
method: 'PATCH',
|
|
2033
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2034
|
+
body: JSON.stringify(body),
|
|
2035
|
+
})
|
|
2036
|
+
|
|
2037
|
+
if (!response.ok) {
|
|
2038
|
+
throw new Error(`Failed to update optimization run: ${await response.text()}`)
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
const data = await parseJsonResponse<{
|
|
2042
|
+
optimizationRun: {
|
|
2043
|
+
id: string
|
|
2044
|
+
status: string
|
|
2045
|
+
startedAt?: string | null
|
|
2046
|
+
finishedAt?: string | null
|
|
2047
|
+
bestScore?: number | null
|
|
2048
|
+
bestCandidateId?: string | null
|
|
2049
|
+
resultPromptVersionId?: string | null
|
|
2050
|
+
metadata?: Record<string, unknown>
|
|
2051
|
+
}
|
|
2052
|
+
}>(response, 'Optimization run update')
|
|
2053
|
+
|
|
2054
|
+
if (hasJsonFlag()) {
|
|
2055
|
+
printJson(data)
|
|
2056
|
+
return
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
const id = sanitizeTerminalText(data.optimizationRun.id)
|
|
2060
|
+
if (action === 'pause') {
|
|
2061
|
+
printLine(`Paused optimization run ${id}`)
|
|
2062
|
+
} else if (action === 'resume') {
|
|
2063
|
+
printLine(`Resumed optimization run ${id}`)
|
|
2064
|
+
} else if (action === 'finish') {
|
|
2065
|
+
printLine(`Finished optimization run ${id}`)
|
|
2066
|
+
} else if (action === 'fail') {
|
|
2067
|
+
printLine(`Marked optimization run ${id} failed`)
|
|
2068
|
+
} else {
|
|
2069
|
+
printLine(`Cancelled optimization run ${id}`)
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
function removeFlagWithValue(args: string[], flag: string): string[] {
|
|
2074
|
+
const filtered: string[] = []
|
|
2075
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2076
|
+
if (args[index] === flag) {
|
|
2077
|
+
index += 1
|
|
2078
|
+
continue
|
|
952
2079
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
2080
|
+
filtered.push(args[index])
|
|
2081
|
+
}
|
|
2082
|
+
return filtered
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function bundledOrizuGepaPythonPath(): string | null {
|
|
2086
|
+
const candidate = fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url))
|
|
2087
|
+
return existsSync(candidate) ? candidate : null
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
async function runGepaOptimization() {
|
|
2091
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
2092
|
+
const baseUrl = getBaseUrl()
|
|
2093
|
+
const token = getStoredAuthTokenForBaseUrl(baseUrl)
|
|
2094
|
+
const python = getArg('--python') || process.env.PYTHON || 'python3'
|
|
2095
|
+
const bundledPythonPath = bundledOrizuGepaPythonPath()
|
|
2096
|
+
let forwardedArgs = removeFlagWithValue(cliArgs.slice(2), '--python')
|
|
2097
|
+
|
|
2098
|
+
if (!forwardedArgs.includes('--project')) {
|
|
2099
|
+
forwardedArgs = ['--project', project, ...forwardedArgs]
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
const pythonPathEntries = [
|
|
2103
|
+
bundledPythonPath,
|
|
2104
|
+
process.env.PYTHONPATH,
|
|
2105
|
+
].filter((entry): entry is string => Boolean(entry))
|
|
2106
|
+
|
|
2107
|
+
const result = spawnSync(python, ['-m', 'orizu_gepa.cli', ...forwardedArgs], {
|
|
2108
|
+
stdio: 'inherit',
|
|
2109
|
+
env: {
|
|
2110
|
+
...process.env,
|
|
2111
|
+
ORIZU_API_URL: baseUrl,
|
|
2112
|
+
ORIZU_TOKEN: token,
|
|
2113
|
+
ORIZU_PROJECT: project,
|
|
2114
|
+
PYTHONPATH: pythonPathEntries.join(delimiter),
|
|
2115
|
+
PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || '1',
|
|
2116
|
+
},
|
|
2117
|
+
})
|
|
2118
|
+
|
|
2119
|
+
if (result.error) {
|
|
2120
|
+
throw result.error
|
|
956
2121
|
}
|
|
957
2122
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
console.warn(`Warning: remote logout failed: ${remoteLogoutError}`)
|
|
2123
|
+
if (result.status !== 0) {
|
|
2124
|
+
throw new Error(`orizu-gepa failed with exit code ${result.status}`)
|
|
961
2125
|
}
|
|
962
|
-
printLine(`Logged out from ${sanitizeTerminalText(baseUrl)}.`)
|
|
963
2126
|
}
|
|
964
2127
|
|
|
965
2128
|
async function listTeams() {
|
|
@@ -1074,6 +2237,26 @@ function readSourceFile(pathArg: string): string {
|
|
|
1074
2237
|
}
|
|
1075
2238
|
}
|
|
1076
2239
|
|
|
2240
|
+
function readSourceBytes(pathArg: string): Buffer {
|
|
2241
|
+
const expandedPath = expandHomePath(pathArg)
|
|
2242
|
+
try {
|
|
2243
|
+
return readFileSync(expandedPath)
|
|
2244
|
+
} catch (error: any) {
|
|
2245
|
+
if (error?.code === 'ENOENT') {
|
|
2246
|
+
throw new Error(`File not found: ${expandedPath}`)
|
|
2247
|
+
}
|
|
2248
|
+
throw new Error(`Failed to read file '${expandedPath}': ${error?.message || String(error)}`)
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function hasJsonFlag(): boolean {
|
|
2253
|
+
return hasArg('--json')
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
function printJson(value: Record<string, unknown>) {
|
|
2257
|
+
printLine(JSON.stringify(value))
|
|
2258
|
+
}
|
|
2259
|
+
|
|
1077
2260
|
function readJsonFile(pathArg: string): Record<string, unknown> {
|
|
1078
2261
|
const raw = readSourceFile(pathArg)
|
|
1079
2262
|
try {
|
|
@@ -1088,6 +2271,218 @@ function readJsonFile(pathArg: string): Record<string, unknown> {
|
|
|
1088
2271
|
}
|
|
1089
2272
|
}
|
|
1090
2273
|
|
|
2274
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
2275
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function stringFromRecord(record: Record<string, unknown>, key: string): string | undefined {
|
|
2279
|
+
const value = record[key]
|
|
2280
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
function safeRelativePath(value: string, fallback: string): string {
|
|
2284
|
+
const raw = value && value.trim() ? value.trim() : fallback
|
|
2285
|
+
if (raw.includes('\0')) {
|
|
2286
|
+
throw new Error(`Unsafe relative path: ${raw}`)
|
|
2287
|
+
}
|
|
2288
|
+
const normalized = normalize(raw)
|
|
2289
|
+
if (isAbsolute(raw) || normalized === '..' || normalized.startsWith('../') || normalized.startsWith('..\\')) {
|
|
2290
|
+
throw new Error(`Unsafe relative path: ${raw}`)
|
|
2291
|
+
}
|
|
2292
|
+
return normalized === '.' ? fallback : normalized
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function sha256Hex(value: string | Buffer): string {
|
|
2296
|
+
return createHash('sha256').update(value).digest('hex')
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function readPromptPrimaryText(manifest: Record<string, unknown>, promptDir: string): {
|
|
2300
|
+
body: string
|
|
2301
|
+
bodyKind: string
|
|
2302
|
+
path: string
|
|
2303
|
+
} {
|
|
2304
|
+
const primaryText = isRecord(manifest.primary_text) ? manifest.primary_text : null
|
|
2305
|
+
const pathValue = primaryText
|
|
2306
|
+
? stringFromRecord(primaryText, 'path')
|
|
2307
|
+
: stringFromRecord(manifest, 'body_file')
|
|
2308
|
+
const bodyPath = safeRelativePath(pathValue || 'prompt.md', 'prompt.md')
|
|
2309
|
+
const bodyKind = primaryText
|
|
2310
|
+
? stringFromRecord(primaryText, 'kind') || stringFromRecord(manifest, 'body_kind') || 'text'
|
|
2311
|
+
: stringFromRecord(manifest, 'body_kind') || 'text'
|
|
2312
|
+
|
|
2313
|
+
return {
|
|
2314
|
+
body: readSourceFile(join(promptDir, bodyPath)),
|
|
2315
|
+
bodyKind,
|
|
2316
|
+
path: bodyPath,
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function readPromptSidecars(manifest: Record<string, unknown>, promptDir: string) {
|
|
2321
|
+
const sidecars = Array.isArray(manifest.sidecars) ? manifest.sidecars : []
|
|
2322
|
+
return sidecars
|
|
2323
|
+
.filter(isRecord)
|
|
2324
|
+
.map(sidecar => {
|
|
2325
|
+
const pathValue = stringFromRecord(sidecar, 'path')
|
|
2326
|
+
if (!pathValue) {
|
|
2327
|
+
throw new Error('Prompt sidecar entries require path')
|
|
2328
|
+
}
|
|
2329
|
+
const relativePath = safeRelativePath(pathValue, pathValue)
|
|
2330
|
+
const content = readSourceFile(join(promptDir, relativePath))
|
|
2331
|
+
const { content: _ignoredContent, content_sha256: _ignoredSnake, contentSha256: _ignoredCamel, ...metadata } = sidecar
|
|
2332
|
+
return {
|
|
2333
|
+
...metadata,
|
|
2334
|
+
type: stringFromRecord(sidecar, 'type') || 'file',
|
|
2335
|
+
path: relativePath,
|
|
2336
|
+
content,
|
|
2337
|
+
contentSha256: sha256Hex(content),
|
|
2338
|
+
}
|
|
2339
|
+
})
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function writeTextFileEnsuringDir(pathArg: string, content: string) {
|
|
2343
|
+
mkdirSync(dirname(pathArg), { recursive: true })
|
|
2344
|
+
writeFileSync(pathArg, content)
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function shouldExcludeArtifactPath(relativePath: string): boolean {
|
|
2348
|
+
const parts = relativePath.split('/')
|
|
2349
|
+
return parts.some(part =>
|
|
2350
|
+
part === '.git' ||
|
|
2351
|
+
part === '.DS_Store' ||
|
|
2352
|
+
part === '__pycache__' ||
|
|
2353
|
+
part === '.pytest_cache'
|
|
2354
|
+
)
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function collectArtifactFiles(sourceDir: string, relativeDir = ''): string[] {
|
|
2358
|
+
const absoluteDir = relativeDir ? join(sourceDir, relativeDir) : sourceDir
|
|
2359
|
+
return readdirSync(absoluteDir, { withFileTypes: true })
|
|
2360
|
+
.flatMap(entry => {
|
|
2361
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name
|
|
2362
|
+
if (shouldExcludeArtifactPath(relativePath)) {
|
|
2363
|
+
return []
|
|
2364
|
+
}
|
|
2365
|
+
if (entry.isDirectory()) {
|
|
2366
|
+
return collectArtifactFiles(sourceDir, relativePath)
|
|
2367
|
+
}
|
|
2368
|
+
return entry.isFile() ? [relativePath] : []
|
|
2369
|
+
})
|
|
2370
|
+
.sort((left, right) => left < right ? -1 : left > right ? 1 : 0)
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
const ZIP_GENERAL_PURPOSE_UTF8 = 0x0800
|
|
2374
|
+
const ZIP_DOS_TIME_2000_01_01 = 0
|
|
2375
|
+
const ZIP_DOS_DATE_2000_01_01 = ((2000 - 1980) << 9) | (1 << 5) | 1
|
|
2376
|
+
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
2377
|
+
let value = index
|
|
2378
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
2379
|
+
value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1)
|
|
2380
|
+
}
|
|
2381
|
+
return value >>> 0
|
|
2382
|
+
})
|
|
2383
|
+
|
|
2384
|
+
function crc32(bytes: Buffer): number {
|
|
2385
|
+
let crc = 0xffffffff
|
|
2386
|
+
for (const byte of bytes) {
|
|
2387
|
+
crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8)
|
|
2388
|
+
}
|
|
2389
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
function createStoredZip(entries: Array<{ path: string; data: Buffer }>): Buffer {
|
|
2393
|
+
const localParts: Buffer[] = []
|
|
2394
|
+
const centralParts: Buffer[] = []
|
|
2395
|
+
let offset = 0
|
|
2396
|
+
|
|
2397
|
+
for (const entry of entries) {
|
|
2398
|
+
const name = Buffer.from(entry.path, 'utf8')
|
|
2399
|
+
const crc = crc32(entry.data)
|
|
2400
|
+
const localHeader = Buffer.alloc(30)
|
|
2401
|
+
localHeader.writeUInt32LE(0x04034b50, 0)
|
|
2402
|
+
localHeader.writeUInt16LE(10, 4)
|
|
2403
|
+
localHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 6)
|
|
2404
|
+
localHeader.writeUInt16LE(0, 8)
|
|
2405
|
+
localHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 10)
|
|
2406
|
+
localHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 12)
|
|
2407
|
+
localHeader.writeUInt32LE(crc, 14)
|
|
2408
|
+
localHeader.writeUInt32LE(entry.data.length, 18)
|
|
2409
|
+
localHeader.writeUInt32LE(entry.data.length, 22)
|
|
2410
|
+
localHeader.writeUInt16LE(name.length, 26)
|
|
2411
|
+
localHeader.writeUInt16LE(0, 28)
|
|
2412
|
+
|
|
2413
|
+
localParts.push(localHeader, name, entry.data)
|
|
2414
|
+
|
|
2415
|
+
const centralHeader = Buffer.alloc(46)
|
|
2416
|
+
centralHeader.writeUInt32LE(0x02014b50, 0)
|
|
2417
|
+
centralHeader.writeUInt16LE(20, 4)
|
|
2418
|
+
centralHeader.writeUInt16LE(10, 6)
|
|
2419
|
+
centralHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 8)
|
|
2420
|
+
centralHeader.writeUInt16LE(0, 10)
|
|
2421
|
+
centralHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 12)
|
|
2422
|
+
centralHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 14)
|
|
2423
|
+
centralHeader.writeUInt32LE(crc, 16)
|
|
2424
|
+
centralHeader.writeUInt32LE(entry.data.length, 20)
|
|
2425
|
+
centralHeader.writeUInt32LE(entry.data.length, 24)
|
|
2426
|
+
centralHeader.writeUInt16LE(name.length, 28)
|
|
2427
|
+
centralHeader.writeUInt16LE(0, 30)
|
|
2428
|
+
centralHeader.writeUInt16LE(0, 32)
|
|
2429
|
+
centralHeader.writeUInt16LE(0, 34)
|
|
2430
|
+
centralHeader.writeUInt16LE(0, 36)
|
|
2431
|
+
centralHeader.writeUInt32LE(0, 38)
|
|
2432
|
+
centralHeader.writeUInt32LE(offset, 42)
|
|
2433
|
+
centralParts.push(centralHeader, name)
|
|
2434
|
+
|
|
2435
|
+
offset += localHeader.length + name.length + entry.data.length
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
const centralDirectoryOffset = offset
|
|
2439
|
+
const centralDirectory = Buffer.concat(centralParts)
|
|
2440
|
+
const endOfCentralDirectory = Buffer.alloc(22)
|
|
2441
|
+
endOfCentralDirectory.writeUInt32LE(0x06054b50, 0)
|
|
2442
|
+
endOfCentralDirectory.writeUInt16LE(0, 4)
|
|
2443
|
+
endOfCentralDirectory.writeUInt16LE(0, 6)
|
|
2444
|
+
endOfCentralDirectory.writeUInt16LE(entries.length, 8)
|
|
2445
|
+
endOfCentralDirectory.writeUInt16LE(entries.length, 10)
|
|
2446
|
+
endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12)
|
|
2447
|
+
endOfCentralDirectory.writeUInt32LE(centralDirectoryOffset, 16)
|
|
2448
|
+
endOfCentralDirectory.writeUInt16LE(0, 20)
|
|
2449
|
+
|
|
2450
|
+
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory])
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
function zipDirectoryToBase64(dirArg: string): { zipBase64: string; contentSha256: string } {
|
|
2454
|
+
const sourceDir = expandHomePath(dirArg)
|
|
2455
|
+
try {
|
|
2456
|
+
const stats = statSync(sourceDir)
|
|
2457
|
+
if (!stats.isDirectory()) {
|
|
2458
|
+
throw new Error(`${sourceDir} is not a directory`)
|
|
2459
|
+
}
|
|
2460
|
+
} catch (error: any) {
|
|
2461
|
+
if (error?.code === 'ENOENT') {
|
|
2462
|
+
throw new Error(`Directory not found: ${sourceDir}`)
|
|
2463
|
+
}
|
|
2464
|
+
throw error
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
const files = collectArtifactFiles(sourceDir)
|
|
2468
|
+
if (files.length === 0) {
|
|
2469
|
+
throw new Error(`Directory contains no artifact files: ${sourceDir}`)
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
const bytes = createStoredZip(files.map(relativePath => ({
|
|
2473
|
+
path: relativePath,
|
|
2474
|
+
data: readFileSync(join(sourceDir, relativePath)),
|
|
2475
|
+
})))
|
|
2476
|
+
return {
|
|
2477
|
+
zipBase64: bytes.toString('base64'),
|
|
2478
|
+
contentSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
function readManifestFile(dirArg: string): Record<string, unknown> {
|
|
2483
|
+
return readJsonFile(join(expandHomePath(dirArg), 'manifest.json'))
|
|
2484
|
+
}
|
|
2485
|
+
|
|
1091
2486
|
async function createAppFromFile() {
|
|
1092
2487
|
const project = getArg('--project')
|
|
1093
2488
|
const name = getArg('--name')
|
|
@@ -1695,6 +3090,36 @@ async function uploadDataset() {
|
|
|
1695
3090
|
}
|
|
1696
3091
|
}
|
|
1697
3092
|
|
|
3093
|
+
async function pushDataset() {
|
|
3094
|
+
const projectArg = getArg('--project')
|
|
3095
|
+
const fileArg = getPositionalArg(2) || getArg('--file')
|
|
3096
|
+
const name = getArg('--name')
|
|
3097
|
+
|
|
3098
|
+
if (!fileArg) {
|
|
3099
|
+
throw new Error('Usage: orizu datasets push <rows.csv|rows.json|rows.jsonl> [--project <team/project>] [--name <name>] [--json]')
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
const file = expandHomePath(fileArg)
|
|
3103
|
+
const project = await resolveProjectSlug(projectArg)
|
|
3104
|
+
const datasetName = name || basename(file)
|
|
3105
|
+
const { rows, sourceType } = parseDatasetFile(file)
|
|
3106
|
+
const data = await createDatasetFromRows(project, datasetName, sourceType, rows)
|
|
3107
|
+
|
|
3108
|
+
if (hasJsonFlag()) {
|
|
3109
|
+
printJson({
|
|
3110
|
+
dataset_id: data.dataset.id,
|
|
3111
|
+
name: data.dataset.name,
|
|
3112
|
+
row_count: data.dataset.rowCount,
|
|
3113
|
+
})
|
|
3114
|
+
return
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`)
|
|
3118
|
+
if (data.dataset.url) {
|
|
3119
|
+
printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`)
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
1698
3123
|
function getDatasetReferenceInput(): string | null {
|
|
1699
3124
|
const fromFlag = getArg('--dataset')
|
|
1700
3125
|
if (fromFlag) {
|
|
@@ -2167,6 +3592,253 @@ async function downloadAnnotations() {
|
|
|
2167
3592
|
printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`)
|
|
2168
3593
|
}
|
|
2169
3594
|
|
|
3595
|
+
function readRunnerManifest(runnerDir: string): { command: string[]; supports_body_kind?: string[] } {
|
|
3596
|
+
const manifestPath = join(runnerDir, 'manifest.json')
|
|
3597
|
+
const raw = readSourceFile(manifestPath)
|
|
3598
|
+
const manifest = JSON.parse(raw) as {
|
|
3599
|
+
command?: unknown
|
|
3600
|
+
supports_body_kind?: unknown
|
|
3601
|
+
supports_body_kinds?: unknown
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
if (!Array.isArray(manifest.command) || !manifest.command.every(item => typeof item === 'string')) {
|
|
3605
|
+
throw new Error(`Runner manifest at ${manifestPath} must include command: string[]`)
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
const supportedBodyKinds = manifest.supports_body_kinds ?? manifest.supports_body_kind
|
|
3609
|
+
if (
|
|
3610
|
+
supportedBodyKinds !== undefined &&
|
|
3611
|
+
(!Array.isArray(supportedBodyKinds) ||
|
|
3612
|
+
!supportedBodyKinds.every(item => typeof item === 'string'))
|
|
3613
|
+
) {
|
|
3614
|
+
throw new Error(`Runner manifest at ${manifestPath} has invalid supports_body_kinds`)
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
return {
|
|
3618
|
+
command: manifest.command,
|
|
3619
|
+
supports_body_kind: supportedBodyKinds as string[] | undefined,
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
const RUNNER_TIMEOUT_MS = 120_000
|
|
3624
|
+
const RUNNER_OUTPUT_MAX_BYTES = 2 * 1024 * 1024
|
|
3625
|
+
const RUNNER_ARTIFACT_MAX_BYTES = 25 * 1024 * 1024
|
|
3626
|
+
const RUNNER_ENV_ALLOWLIST = new Set([
|
|
3627
|
+
'PATH',
|
|
3628
|
+
'SystemRoot',
|
|
3629
|
+
'WINDIR',
|
|
3630
|
+
'HOME',
|
|
3631
|
+
'TMPDIR',
|
|
3632
|
+
'TEMP',
|
|
3633
|
+
'TMP',
|
|
3634
|
+
'LANG',
|
|
3635
|
+
'LC_ALL',
|
|
3636
|
+
'PYTHONPATH',
|
|
3637
|
+
'NODE_PATH',
|
|
3638
|
+
'ANTHROPIC_API_KEY',
|
|
3639
|
+
'OPENAI_API_KEY',
|
|
3640
|
+
'GEMINI_API_KEY',
|
|
3641
|
+
'GOOGLE_API_KEY',
|
|
3642
|
+
])
|
|
3643
|
+
|
|
3644
|
+
function runnerSubprocessEnv(inputPath: string, outputPath: string): NodeJS.ProcessEnv {
|
|
3645
|
+
const env = {} as NodeJS.ProcessEnv
|
|
3646
|
+
for (const key of RUNNER_ENV_ALLOWLIST) {
|
|
3647
|
+
const value = process.env[key]
|
|
3648
|
+
if (value !== undefined) {
|
|
3649
|
+
env[key] = value
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
env.ORIZU_RUNNER_INPUT_PATH = inputPath
|
|
3653
|
+
env.ORIZU_RUNNER_OUTPUT_PATH = outputPath
|
|
3654
|
+
return env
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
function boundedRunnerOutput(value: string | Buffer | null | undefined): string {
|
|
3658
|
+
if (!value) return ''
|
|
3659
|
+
const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value)
|
|
3660
|
+
if (buffer.byteLength <= RUNNER_OUTPUT_MAX_BYTES) {
|
|
3661
|
+
return buffer.toString('utf8')
|
|
3662
|
+
}
|
|
3663
|
+
return `${buffer.subarray(0, RUNNER_OUTPUT_MAX_BYTES).toString('utf8')}\n[truncated]`
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
async function materializeRunnerVersion(runnerVersionId: string): Promise<{ runnerDir: string; cleanup: () => void }> {
|
|
3667
|
+
const response = await authedFetch(`/api/cli/runner-versions/${encodeURIComponent(runnerVersionId)}/download`)
|
|
3668
|
+
if (!response.ok) {
|
|
3669
|
+
throw new Error(`Failed to download runner version: ${await response.text()}`)
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-version-'))
|
|
3673
|
+
const zipPath = join(tempDir, 'runner.zip')
|
|
3674
|
+
const runnerDir = join(tempDir, 'runner')
|
|
3675
|
+
const zipBytes = new Uint8Array(await response.arrayBuffer())
|
|
3676
|
+
if (zipBytes.byteLength > RUNNER_ARTIFACT_MAX_BYTES) {
|
|
3677
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
3678
|
+
throw new Error(`Runner artifact exceeds ${RUNNER_ARTIFACT_MAX_BYTES} bytes`)
|
|
3679
|
+
}
|
|
3680
|
+
writeFileSync(zipPath, zipBytes)
|
|
3681
|
+
|
|
3682
|
+
const result = spawnSync('unzip', ['-q', zipPath, '-d', runnerDir], {
|
|
3683
|
+
encoding: 'utf8',
|
|
3684
|
+
})
|
|
3685
|
+
if (result.error) {
|
|
3686
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
3687
|
+
throw result.error
|
|
3688
|
+
}
|
|
3689
|
+
if (result.status !== 0) {
|
|
3690
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
3691
|
+
throw new Error(`unzip failed: ${sanitizeTerminalText(result.stderr || result.stdout || '')}`)
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
return {
|
|
3695
|
+
runnerDir,
|
|
3696
|
+
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
async function runnersExec() {
|
|
3701
|
+
const prompt = getArg('--prompt')
|
|
3702
|
+
const promptVersion = getArg('--prompt-version')
|
|
3703
|
+
const runnerVersion = getArg('--runner-version')
|
|
3704
|
+
const scorerVersion = getArg('--scorer-version')
|
|
3705
|
+
const datasetVersion = getArg('--dataset-version')
|
|
3706
|
+
const splitSet = getArg('--split-set')
|
|
3707
|
+
const split = getArg('--split')
|
|
3708
|
+
const runnerDirArg = getArg('--runner-dir')
|
|
3709
|
+
const outArg = getArg('--out')
|
|
3710
|
+
|
|
3711
|
+
if (
|
|
3712
|
+
(!prompt && !promptVersion && !scorerVersion) ||
|
|
3713
|
+
(scorerVersion && (prompt || promptVersion)) ||
|
|
3714
|
+
(promptVersion && !runnerVersion) ||
|
|
3715
|
+
!datasetVersion ||
|
|
3716
|
+
!splitSet ||
|
|
3717
|
+
!split ||
|
|
3718
|
+
!outArg
|
|
3719
|
+
) {
|
|
3720
|
+
throw new Error(
|
|
3721
|
+
'Usage: orizu runners exec (--prompt <prompt> | --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>'
|
|
3722
|
+
)
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
const query = new URLSearchParams()
|
|
3726
|
+
if (scorerVersion) {
|
|
3727
|
+
query.set('scorerVersion', scorerVersion)
|
|
3728
|
+
} else if (promptVersion) {
|
|
3729
|
+
query.set('promptVersion', promptVersion)
|
|
3730
|
+
} else if (prompt) {
|
|
3731
|
+
query.set('prompt', prompt)
|
|
3732
|
+
}
|
|
3733
|
+
if (runnerVersion) {
|
|
3734
|
+
query.set('runnerVersion', runnerVersion)
|
|
3735
|
+
}
|
|
3736
|
+
query.set('datasetVersion', datasetVersion)
|
|
3737
|
+
query.set('splitSet', splitSet)
|
|
3738
|
+
query.set('split', split)
|
|
3739
|
+
const contextResponse = await authedFetch(`/api/cli/runners/exec-context?${query.toString()}`)
|
|
3740
|
+
if (!contextResponse.ok) {
|
|
3741
|
+
throw new Error(`Failed to fetch runner execution context: ${await contextResponse.text()}`)
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
const context = await parseJsonResponse<RunnerExecContext>(contextResponse, 'Runner exec context')
|
|
3745
|
+
const materializedRunner = runnerDirArg
|
|
3746
|
+
? { runnerDir: expandHomePath(runnerDirArg), cleanup: () => {} }
|
|
3747
|
+
: await materializeRunnerVersion(runnerVersion || context.prompt.runnerVersionId)
|
|
3748
|
+
const runnerDir = materializedRunner.runnerDir
|
|
3749
|
+
const manifest = readRunnerManifest(runnerDir)
|
|
3750
|
+
|
|
3751
|
+
try {
|
|
3752
|
+
if (manifest.supports_body_kind && !manifest.supports_body_kind.includes(context.prompt.bodyKind)) {
|
|
3753
|
+
throw new Error(
|
|
3754
|
+
`Runner does not support prompt body kind '${context.prompt.bodyKind}'. Supported kinds: ${manifest.supports_body_kind.join(', ')}`
|
|
3755
|
+
)
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
const resultLines: string[] = []
|
|
3759
|
+
for (const row of context.rows) {
|
|
3760
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-'))
|
|
3761
|
+
const inputPath = join(tempDir, 'input.json')
|
|
3762
|
+
const outputPath = join(tempDir, 'output.json')
|
|
3763
|
+
|
|
3764
|
+
try {
|
|
3765
|
+
const modelOutput = row.row.model_output ?? row.row.modelOutput ?? row.row.output ?? null
|
|
3766
|
+
writeFileSync(inputPath, JSON.stringify({
|
|
3767
|
+
row: row.row,
|
|
3768
|
+
prompt: {
|
|
3769
|
+
body: context.prompt.body,
|
|
3770
|
+
body_kind: context.prompt.bodyKind,
|
|
3771
|
+
provider_settings: context.prompt.providerSettings,
|
|
3772
|
+
},
|
|
3773
|
+
subject: scorerVersion
|
|
3774
|
+
? {
|
|
3775
|
+
type: 'scorer_row',
|
|
3776
|
+
row_id: row.id,
|
|
3777
|
+
scorer_version_id: context.scorer?.versionId || scorerVersion,
|
|
3778
|
+
prompt_version_id: context.prompt.promptVersionId,
|
|
3779
|
+
}
|
|
3780
|
+
: {
|
|
3781
|
+
type: 'prompt_version',
|
|
3782
|
+
row_id: row.id,
|
|
3783
|
+
prompt_version_id: context.prompt.promptVersionId,
|
|
3784
|
+
},
|
|
3785
|
+
scorer: scorerVersion
|
|
3786
|
+
? {
|
|
3787
|
+
version_id: context.scorer?.versionId || scorerVersion,
|
|
3788
|
+
metric_key: context.scorer?.metricKey || 'score',
|
|
3789
|
+
higher_is_better: context.scorer?.higherIsBetter ?? true,
|
|
3790
|
+
}
|
|
3791
|
+
: null,
|
|
3792
|
+
model_output: modelOutput,
|
|
3793
|
+
prompt_version_id: context.prompt.promptVersionId,
|
|
3794
|
+
runner_version_id: context.prompt.runnerVersionId,
|
|
3795
|
+
run_id: null,
|
|
3796
|
+
}))
|
|
3797
|
+
|
|
3798
|
+
const result = spawnSync(manifest.command[0], manifest.command.slice(1), {
|
|
3799
|
+
cwd: runnerDir,
|
|
3800
|
+
env: runnerSubprocessEnv(inputPath, outputPath),
|
|
3801
|
+
encoding: 'utf8',
|
|
3802
|
+
maxBuffer: RUNNER_OUTPUT_MAX_BYTES,
|
|
3803
|
+
timeout: RUNNER_TIMEOUT_MS,
|
|
3804
|
+
})
|
|
3805
|
+
|
|
3806
|
+
if (result.error) {
|
|
3807
|
+
throw result.error
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
if (result.status !== 0) {
|
|
3811
|
+
throw new Error(
|
|
3812
|
+
`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(boundedRunnerOutput(result.stderr || result.stdout))}`
|
|
3813
|
+
)
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8')) as Record<string, unknown>
|
|
3817
|
+
resultLines.push(JSON.stringify({
|
|
3818
|
+
row_id: row.id,
|
|
3819
|
+
prompt_version_id: context.prompt.promptVersionId,
|
|
3820
|
+
runner_version_id: context.prompt.runnerVersionId,
|
|
3821
|
+
...(scorerVersion ? { scorer_version_id: context.scorer?.versionId || scorerVersion } : {}),
|
|
3822
|
+
...runnerOutput,
|
|
3823
|
+
}))
|
|
3824
|
+
} finally {
|
|
3825
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
const outPath = expandHomePath(outArg)
|
|
3830
|
+
const resultJsonl = `${resultLines.join('\n')}${resultLines.length > 0 ? '\n' : ''}`
|
|
3831
|
+
if (outPath.endsWith('.gz')) {
|
|
3832
|
+
writeFileSync(outPath, gzipSync(Buffer.from(resultJsonl, 'utf8')))
|
|
3833
|
+
} else {
|
|
3834
|
+
writeFileSync(outPath, resultJsonl)
|
|
3835
|
+
}
|
|
3836
|
+
printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`)
|
|
3837
|
+
} finally {
|
|
3838
|
+
materializedRunner.cleanup()
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
|
|
2170
3842
|
export async function main(rawArgs = process.argv.slice(2)) {
|
|
2171
3843
|
const parsed = parseGlobalFlags(rawArgs)
|
|
2172
3844
|
setGlobalFlags(parsed.flags)
|
|
@@ -2182,6 +3854,7 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2182
3854
|
|
|
2183
3855
|
if (!command) {
|
|
2184
3856
|
printUsage()
|
|
3857
|
+
printOptimizationUsage()
|
|
2185
3858
|
process.exit(1)
|
|
2186
3859
|
}
|
|
2187
3860
|
|
|
@@ -2200,6 +3873,16 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2200
3873
|
return
|
|
2201
3874
|
}
|
|
2202
3875
|
|
|
3876
|
+
if (command === 'env') {
|
|
3877
|
+
await printEnv()
|
|
3878
|
+
return
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
if (command === 'log') {
|
|
3882
|
+
await logOptimizationEvent()
|
|
3883
|
+
return
|
|
3884
|
+
}
|
|
3885
|
+
|
|
2203
3886
|
if (command === 'teams' && subcommand === 'list') {
|
|
2204
3887
|
await listTeams()
|
|
2205
3888
|
return
|
|
@@ -2238,6 +3921,120 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2238
3921
|
return
|
|
2239
3922
|
}
|
|
2240
3923
|
|
|
3924
|
+
if (command === 'prompts' && subcommand === 'list') {
|
|
3925
|
+
await listPrompts()
|
|
3926
|
+
return
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
if (command === 'prompts' && subcommand === 'pull') {
|
|
3930
|
+
await pullPromptArtifact('prompt')
|
|
3931
|
+
return
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
if (command === 'prompts' && subcommand === 'push') {
|
|
3935
|
+
await pushPromptArtifact('prompt')
|
|
3936
|
+
return
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
if (command === 'prompts' && subcommand === 'labels' && cliArgs[2] === 'set') {
|
|
3940
|
+
await setPromptLabel()
|
|
3941
|
+
return
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
if (command === 'prompts' && subcommand === 'scorers' && cliArgs[2] === 'set-headline') {
|
|
3945
|
+
await bindPromptScorer('headline')
|
|
3946
|
+
return
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
if (command === 'prompts' && subcommand === 'scorers' && cliArgs[2] === 'add') {
|
|
3950
|
+
await bindPromptScorer('tracked')
|
|
3951
|
+
return
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
if (command === 'judges' && subcommand === 'list') {
|
|
3955
|
+
await listJudges()
|
|
3956
|
+
return
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
if (command === 'judges' && subcommand === 'pull') {
|
|
3960
|
+
await pullPromptArtifact('judge')
|
|
3961
|
+
return
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
if (command === 'judges' && subcommand === 'push') {
|
|
3965
|
+
await pushPromptArtifact('judge')
|
|
3966
|
+
return
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
if (command === 'scorers' && subcommand === 'list') {
|
|
3970
|
+
await listScorers()
|
|
3971
|
+
return
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
if (command === 'scorers' && subcommand === 'register') {
|
|
3975
|
+
await registerScorer()
|
|
3976
|
+
return
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
if (command === 'scorers' && subcommand === 'detail') {
|
|
3980
|
+
await showScorerDetail()
|
|
3981
|
+
return
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
if (command === 'scorers' && subcommand === 'labels' && cliArgs[2] === 'set') {
|
|
3985
|
+
await setScorerLabel()
|
|
3986
|
+
return
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
if (command === 'runners' && subcommand === 'push') {
|
|
3990
|
+
await pushRunnerArtifact('runner')
|
|
3991
|
+
return
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
if (command === 'runners' && subcommand === 'exec') {
|
|
3995
|
+
await runnersExec()
|
|
3996
|
+
return
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
if (command === 'optimizers' && subcommand === 'push') {
|
|
4000
|
+
await pushRunnerArtifact('optimizer')
|
|
4001
|
+
return
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
if (command === 'runs' && subcommand === 'submit') {
|
|
4005
|
+
await submitRunResults()
|
|
4006
|
+
return
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
if (command === 'scores' && subcommand === 'submit') {
|
|
4010
|
+
await submitScoreResults()
|
|
4011
|
+
return
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
if (command === 'optimizations' && subcommand === 'start') {
|
|
4015
|
+
await startOptimizationRun()
|
|
4016
|
+
return
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
if (command === 'optimizations' && subcommand === 'run-gepa') {
|
|
4020
|
+
await runGepaOptimization()
|
|
4021
|
+
return
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
if (
|
|
4025
|
+
command === 'optimizations' &&
|
|
4026
|
+
(
|
|
4027
|
+
subcommand === 'pause' ||
|
|
4028
|
+
subcommand === 'resume' ||
|
|
4029
|
+
subcommand === 'finish' ||
|
|
4030
|
+
subcommand === 'fail' ||
|
|
4031
|
+
subcommand === 'cancel'
|
|
4032
|
+
)
|
|
4033
|
+
) {
|
|
4034
|
+
await updateOptimizationRunLifecycle(subcommand)
|
|
4035
|
+
return
|
|
4036
|
+
}
|
|
4037
|
+
|
|
2241
4038
|
if (command === 'apps' && subcommand === 'list') {
|
|
2242
4039
|
await listApps()
|
|
2243
4040
|
return
|
|
@@ -2298,6 +4095,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2298
4095
|
return
|
|
2299
4096
|
}
|
|
2300
4097
|
|
|
4098
|
+
if (command === 'datasets' && subcommand === 'push') {
|
|
4099
|
+
await pushDataset()
|
|
4100
|
+
return
|
|
4101
|
+
}
|
|
4102
|
+
|
|
2301
4103
|
if (command === 'datasets' && subcommand === 'download') {
|
|
2302
4104
|
await downloadDataset()
|
|
2303
4105
|
return
|
|
@@ -2308,6 +4110,17 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2308
4110
|
return
|
|
2309
4111
|
}
|
|
2310
4112
|
|
|
4113
|
+
const datasetsAction = cliArgs[2]
|
|
4114
|
+
if (command === 'datasets' && subcommand === 'versions' && datasetsAction === 'create') {
|
|
4115
|
+
await createDatasetVersion()
|
|
4116
|
+
return
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
if (command === 'datasets' && subcommand === 'splits' && datasetsAction === 'create') {
|
|
4120
|
+
await createDatasetSplitSet()
|
|
4121
|
+
return
|
|
4122
|
+
}
|
|
4123
|
+
|
|
2311
4124
|
if (command === 'datasets' && subcommand === 'edit-rows') {
|
|
2312
4125
|
await editDatasetRows()
|
|
2313
4126
|
return
|
|
@@ -2339,6 +4152,7 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
2339
4152
|
}
|
|
2340
4153
|
|
|
2341
4154
|
printUsage()
|
|
4155
|
+
printOptimizationUsage()
|
|
2342
4156
|
process.exit(1)
|
|
2343
4157
|
}
|
|
2344
4158
|
|