orizu 0.5.20 → 0.5.21
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/archive-artifacts-cli.d.ts +50 -0
- package/dist/archive-artifacts-cli.d.ts.map +1 -0
- package/dist/artifact-archive.d.ts +48 -0
- package/dist/artifact-archive.d.ts.map +1 -0
- package/dist/artifact-pull.d.ts +22 -0
- package/dist/artifact-pull.d.ts.map +1 -0
- package/dist/banner.d.ts +3 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/connectors-cli.d.ts +31 -0
- package/dist/connectors-cli.d.ts.map +1 -0
- package/dist/credentials.d.ts +46 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/csv.d.ts +49 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/dataset-download.d.ts +6 -0
- package/dist/dataset-download.d.ts.map +1 -0
- package/dist/dataset-edit-rows.d.ts +10 -0
- package/dist/dataset-edit-rows.d.ts.map +1 -0
- package/dist/dataset-upload-snapshot.d.ts +13 -0
- package/dist/dataset-upload-snapshot.d.ts.map +1 -0
- package/dist/daytona-slice-rehearsal.d.ts +89 -0
- package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
- package/dist/daytona-slice.d.ts +106 -0
- package/dist/daytona-slice.d.ts.map +1 -0
- package/dist/deploy-key-merge-core.d.ts +121 -0
- package/dist/deploy-key-merge-core.d.ts.map +1 -0
- package/dist/diff-comments-cli.d.ts +12 -0
- package/dist/diff-comments-cli.d.ts.map +1 -0
- package/dist/diff-comments-cli.js +3 -0
- package/dist/egress-allowlist-cli.d.ts +23 -0
- package/dist/egress-allowlist-cli.d.ts.map +1 -0
- package/dist/egress-policy.d.ts +108 -0
- package/dist/egress-policy.d.ts.map +1 -0
- package/dist/error-response.d.ts +9 -0
- package/dist/error-response.d.ts.map +1 -0
- package/dist/file-parser.d.ts +5 -0
- package/dist/file-parser.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.d.ts +9 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.js +48 -2
- package/dist/gepa-python-command.d.ts +6 -0
- package/dist/gepa-python-command.d.ts.map +1 -0
- package/dist/gepa-python-paths.d.ts +6 -0
- package/dist/gepa-python-paths.d.ts.map +1 -0
- package/dist/gepa-python-paths.js +6 -3
- package/dist/git-credential.d.ts +69 -0
- package/dist/git-credential.d.ts.map +1 -0
- package/dist/git-recovery-environment.d.ts +31 -0
- package/dist/git-recovery-environment.d.ts.map +1 -0
- package/dist/github-setup.d.ts +122 -0
- package/dist/github-setup.d.ts.map +1 -0
- package/dist/global-flags.d.ts +12 -0
- package/dist/global-flags.d.ts.map +1 -0
- package/dist/help.d.ts +46 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +91 -23
- package/dist/hosted-boot.d.ts +273 -0
- package/dist/hosted-boot.d.ts.map +1 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
- package/dist/hosted-bootstrap.d.ts +188 -0
- package/dist/hosted-bootstrap.d.ts.map +1 -0
- package/dist/hosted-broker-server.d.ts +50 -0
- package/dist/hosted-broker-server.d.ts.map +1 -0
- package/dist/hosted-git-http-server.d.ts +36 -0
- package/dist/hosted-git-http-server.d.ts.map +1 -0
- package/dist/hosted-harness-claude.d.ts +195 -0
- package/dist/hosted-harness-claude.d.ts.map +1 -0
- package/dist/hosted-harness-opencode.d.ts +227 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -0
- package/dist/hosted-harness.d.ts +118 -0
- package/dist/hosted-harness.d.ts.map +1 -0
- package/dist/hosted-harvest.d.ts +68 -0
- package/dist/hosted-harvest.d.ts.map +1 -0
- package/dist/hosted-headless.d.ts +72 -0
- package/dist/hosted-headless.d.ts.map +1 -0
- package/dist/hosted-loop-lifecycle.d.ts +206 -0
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
- package/dist/hosted-loop.d.ts +170 -0
- package/dist/hosted-loop.d.ts.map +1 -0
- package/dist/hosted-run-event-sink.d.ts +222 -0
- package/dist/hosted-run-event-sink.d.ts.map +1 -0
- package/dist/hosted-runtime-assets.d.ts +186 -0
- package/dist/hosted-runtime-assets.d.ts.map +1 -0
- package/dist/hosted-session-cli.d.ts +258 -0
- package/dist/hosted-session-cli.d.ts.map +1 -0
- package/dist/hosted-skill-staging.d.ts +73 -0
- package/dist/hosted-skill-staging.d.ts.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -92
- package/dist/instruction-set-gepa-launch.d.ts +3 -0
- package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
- package/dist/instruction-set-gepa-launch.js +15 -0
- package/dist/instruction-set-loader.d.ts +6 -0
- package/dist/instruction-set-loader.d.ts.map +1 -0
- package/dist/instruction-set-loader.js +59 -0
- package/dist/instruction-set-manifest.d.ts +14 -0
- package/dist/instruction-set-manifest.d.ts.map +1 -0
- package/dist/instruction-set-manifest.js +162 -0
- package/dist/instruction-sets-cli.d.ts +40 -0
- package/dist/instruction-sets-cli.d.ts.map +1 -0
- package/dist/instruction-sets-cli.js +361 -0
- package/dist/json-response.d.ts +4 -0
- package/dist/json-response.d.ts.map +1 -0
- package/dist/jsonl-stream.d.ts +8 -0
- package/dist/jsonl-stream.d.ts.map +1 -0
- package/dist/kill-switch-cli.d.ts +30 -0
- package/dist/kill-switch-cli.d.ts.map +1 -0
- package/dist/loopback-serve.d.ts +25 -0
- package/dist/loopback-serve.d.ts.map +1 -0
- package/dist/manifests-cli.d.ts +22 -0
- package/dist/manifests-cli.d.ts.map +1 -0
- package/dist/markdown-report-input.d.ts +7 -0
- package/dist/markdown-report-input.d.ts.map +1 -0
- package/dist/merge-job.d.ts +155 -0
- package/dist/merge-job.d.ts.map +1 -0
- package/dist/model-configs-cli.d.ts +19 -0
- package/dist/model-configs-cli.d.ts.map +1 -0
- package/dist/model-configs-cli.js +104 -0
- package/dist/optimization-export-cli.d.ts +19 -0
- package/dist/optimization-export-cli.d.ts.map +1 -0
- package/dist/optimizations-list-cli.d.ts +20 -0
- package/dist/optimizations-list-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.d.ts +8 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.js +34 -0
- package/dist/option-validation.d.ts +15 -0
- package/dist/option-validation.d.ts.map +1 -0
- package/dist/preview-runtime.d.ts +63 -0
- package/dist/preview-runtime.d.ts.map +1 -0
- package/dist/prompt-draft-push.d.ts +60 -0
- package/dist/prompt-draft-push.d.ts.map +1 -0
- package/dist/prompt-label-cli.d.ts +11 -0
- package/dist/prompt-label-cli.d.ts.map +1 -0
- package/dist/prompt-label-cli.js +29 -0
- package/dist/prompt-length-wire.d.ts +28 -0
- package/dist/prompt-length-wire.d.ts.map +1 -0
- package/dist/prompt-report-cli.d.ts +10 -0
- package/dist/prompt-report-cli.d.ts.map +1 -0
- package/dist/prompt-summary-table.d.ts +15 -0
- package/dist/prompt-summary-table.d.ts.map +1 -0
- package/dist/prompt-summary-table.js +6 -0
- package/dist/registration-tag-warning.d.ts +32 -0
- package/dist/registration-tag-warning.d.ts.map +1 -0
- package/dist/report-comments-cli.d.ts +16 -0
- package/dist/report-comments-cli.d.ts.map +1 -0
- package/dist/report-markdown-blankness.d.ts +2 -0
- package/dist/report-markdown-blankness.d.ts.map +1 -0
- package/dist/report-replacement-warning.d.ts +17 -0
- package/dist/report-replacement-warning.d.ts.map +1 -0
- package/dist/repository-backup-core.d.ts +225 -0
- package/dist/repository-backup-core.d.ts.map +1 -0
- package/dist/repository-recovery-manifest.d.ts +83 -0
- package/dist/repository-recovery-manifest.d.ts.map +1 -0
- package/dist/run-event-digest.d.ts +74 -0
- package/dist/run-event-digest.d.ts.map +1 -0
- package/dist/runner-dir-verify.d.ts +66 -0
- package/dist/runner-dir-verify.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.d.ts +24 -0
- package/dist/runner-instruction-set-contract.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.js +50 -0
- package/dist/sandbox-hygiene.d.ts +45 -0
- package/dist/sandbox-hygiene.d.ts.map +1 -0
- package/dist/sandbox-provider.d.ts +193 -0
- package/dist/sandbox-provider.d.ts.map +1 -0
- package/dist/scorer-draft-push.d.ts +50 -0
- package/dist/scorer-draft-push.d.ts.map +1 -0
- package/dist/scorers-cli.d.ts +20 -0
- package/dist/scorers-cli.d.ts.map +1 -0
- package/dist/scorers-cli.js +22 -0
- package/dist/scorers-verify-parity-bridge.d.ts +16 -0
- package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
- package/dist/scorers-verify-parity-bridge.js +90 -0
- package/dist/scorers-verify-parity.d.ts +81 -0
- package/dist/scorers-verify-parity.d.ts.map +1 -0
- package/dist/scorers-verify-parity.js +502 -0
- package/dist/scores-accept-cli.d.ts +21 -0
- package/dist/scores-accept-cli.d.ts.map +1 -0
- package/dist/secret-redaction.d.ts +47 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/session-timeline.d.ts +54 -0
- package/dist/session-timeline.d.ts.map +1 -0
- package/dist/setup-prompt.d.ts +5 -0
- package/dist/setup-prompt.d.ts.map +1 -0
- package/dist/skill-installer.d.ts +68 -0
- package/dist/skill-installer.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.d.ts +7 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.js +60 -0
- package/dist/smoke.d.ts +102 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/start-intent.d.ts +12 -0
- package/dist/start-intent.d.ts.map +1 -0
- package/dist/task-assignment-manifest.d.ts +15 -0
- package/dist/task-assignment-manifest.d.ts.map +1 -0
- package/dist/task-create-error.d.ts +18 -0
- package/dist/task-create-error.d.ts.map +1 -0
- package/dist/team-connectors-cli.d.ts +29 -0
- package/dist/team-connectors-cli.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vercel-sandbox-provider.d.ts +156 -0
- package/dist/vercel-sandbox-provider.d.ts.map +1 -0
- package/dist/workbench-cli.d.ts +233 -0
- package/dist/workbench-cli.d.ts.map +1 -0
- package/dist/workspace-sync.d.ts +207 -0
- package/dist/workspace-sync.d.ts.map +1 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/zip-draft-push.d.ts +55 -0
- package/dist/zip-draft-push.d.ts.map +1 -0
- package/package.json +14 -1
- package/requirements/README.md +138 -0
- package/requirements/skilled-proposer.in +66 -0
- package/requirements/skilled-proposer.lock +265 -0
- package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
- package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
- package/src/diff-comments-cli.ts +4 -0
- package/src/gepa-engine-dispatch.ts +42 -2
- package/src/gepa-python-paths.ts +6 -3
- package/src/help.ts +93 -23
- package/src/index.ts +99 -126
- package/src/instruction-set-gepa-launch.ts +28 -0
- package/src/instruction-set-loader.ts +37 -0
- package/src/instruction-set-manifest.ts +120 -0
- package/src/instruction-sets-cli.ts +306 -0
- package/src/model-configs-cli.ts +119 -0
- package/src/optimizations-promote-cli.ts +41 -0
- package/src/prompt-label-cli.ts +55 -0
- package/src/prompt-summary-table.ts +12 -0
- package/src/runner-instruction-set-contract.ts +64 -0
- package/src/scorers-cli.ts +40 -0
- package/src/scorers-verify-parity-bridge.ts +90 -0
- package/src/scorers-verify-parity.ts +564 -0
- package/src/skilled-proposer-launch.ts +89 -0
- package/src/skilled-proposer-venv-manager.mjs +757 -0
- package/tsconfig.json +2 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +2 -1
- package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +8 -2
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +17 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +100 -8
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +92 -12
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +100 -22
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +17 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +2 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +4 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
- package/vendor/skills/orizu-cli/SKILL.md +2 -0
- package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +25 -0
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +108 -8
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { authedFetch } from './http.js'
|
|
2
|
+
import { loadInstructionSetManifest, MAX_INSTRUCTION_SET_COMPONENT_BYTES, type InstructionSetManifest } from './instruction-set-manifest.js'
|
|
3
|
+
import { sanitizeHumanInlineText, sanitizeTerminalText } from './json-response.js'
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
export interface InstructionSetsCommandIo {
|
|
9
|
+
json: boolean
|
|
10
|
+
print: (line: string) => void
|
|
11
|
+
resolveProjectSlug: (provided: string | null) => Promise<string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function positionalArgs(args: string[]): string[] {
|
|
15
|
+
const values: string[] = []
|
|
16
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
17
|
+
const value = args[index]!
|
|
18
|
+
if (value.startsWith('--')) {
|
|
19
|
+
if (argValue(args, value) !== null) index += 1
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
values.push(value)
|
|
23
|
+
}
|
|
24
|
+
return values
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function argValue(args: string[], flag: string): string | null {
|
|
28
|
+
const index = args.indexOf(flag)
|
|
29
|
+
return index === -1 || !args[index + 1] || args[index + 1]!.startsWith('--')
|
|
30
|
+
? null
|
|
31
|
+
: args[index + 1]!
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function responsePayload(response: Response, action: string): Promise<Record<string, unknown>> {
|
|
35
|
+
const payload = await response.json().catch(() => ({})) as Record<string, unknown>
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`${action} failed (${response.status}): ${typeof payload.error === 'string' ? payload.error : response.statusText}`)
|
|
38
|
+
}
|
|
39
|
+
return payload
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/u
|
|
43
|
+
export interface SyncComponent { body?: string; repoPath?: string; contentSha?: string; commitSha?: string }
|
|
44
|
+
export interface SyncMaterial { profileVersionId: string; versionNumber: number; modelConfigIdentity: string; resolvedFrom: string; components: Record<string, SyncComponent> }
|
|
45
|
+
export interface SyncProfile { modelConfigIdentity: string; resolvedFrom: string; production: SyncMaterial | null }
|
|
46
|
+
export interface SyncSet { name: string; slug?: string; description?: string | null; shape: string[]; default: SyncMaterial; profiles: SyncProfile[]; filteredTo?: string[] }
|
|
47
|
+
function safeSegment(value: string) { if (!SAFE_SEGMENT.test(value) || value === '.' || value === '..' || value.startsWith('.')) throw new Error('instruction_set_path_unsafe') }
|
|
48
|
+
function slug(identity: string) { return identity.replaceAll('/', '__').replace(/[^A-Za-z0-9._-]/gu, '_') }
|
|
49
|
+
function stable(value: unknown): string { return `${JSON.stringify(value, null, 2)}\n` }
|
|
50
|
+
function sha256(value: string): string { return createHash('sha256').update(value, 'utf8').digest('hex') }
|
|
51
|
+
|
|
52
|
+
export interface SyncFileOps { writeFileSync: typeof writeFileSync; renameSync?: typeof renameSync }
|
|
53
|
+
|
|
54
|
+
function writeMaterial(root: string, material: SyncMaterial, shape: string[], manifestRoot: string, fileOps: SyncFileOps = { writeFileSync }) {
|
|
55
|
+
const files: Record<string, string> = {}
|
|
56
|
+
const syncedContentSha256: Record<string, string> = {}
|
|
57
|
+
const pinnedComponents: Record<string, unknown> = {}
|
|
58
|
+
for (const key of [...shape].sort()) {
|
|
59
|
+
safeSegment(key)
|
|
60
|
+
const component = material.components[key]
|
|
61
|
+
if (typeof component?.body === 'string') {
|
|
62
|
+
const relative = `${manifestRoot}/${key}.md`
|
|
63
|
+
files[key] = relative
|
|
64
|
+
const target = join(root, ...relative.split('/'))
|
|
65
|
+
mkdirSync(join(target, '..'), { recursive: true })
|
|
66
|
+
fileOps.writeFileSync(target, component.body)
|
|
67
|
+
syncedContentSha256[key] = sha256(component.body)
|
|
68
|
+
} else if (component?.repoPath && component?.contentSha && component?.commitSha) pinnedComponents[key] = { repoPath: component.repoPath, contentSha: component.contentSha, commitSha: component.commitSha }
|
|
69
|
+
else throw new Error('instruction_set_unresolvable')
|
|
70
|
+
}
|
|
71
|
+
return { profileVersionId: material.profileVersionId, versionNumber: material.versionNumber, modelConfigIdentity: material.modelConfigIdentity, resolvedFrom: material.resolvedFrom, files, syncedContentSha256, ...(Object.keys(pinnedComponents).length ? { pinnedComponents } : {}) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Only the scratch paths THIS run created are ever removed; a user's own
|
|
75
|
+
// `.<set>.*.bak` in --out is never touched (PR #1668 review).
|
|
76
|
+
function removeOwnScratch(paths: Array<string | null>) {
|
|
77
|
+
for (const path of paths) if (path && existsSync(path)) rmSync(path, { recursive: true, force: true })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function existingDestinationIsSynced(destination: string, name: string) {
|
|
81
|
+
try {
|
|
82
|
+
if (!statSync(destination).isDirectory()) return false
|
|
83
|
+
const manifest = JSON.parse(readFileSync(join(destination, 'manifest.json'), 'utf8')) as { manifestVersion?: number; name?: string }
|
|
84
|
+
return manifest.manifestVersion === 1 && manifest.name === name
|
|
85
|
+
} catch { return false }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function syncToDisk(out: string, set: SyncSet, fileOps: SyncFileOps = { writeFileSync }) {
|
|
89
|
+
safeSegment(set.name)
|
|
90
|
+
for (const key of set.shape) safeSegment(key)
|
|
91
|
+
const profileSlugs = new Set<string>()
|
|
92
|
+
for (const profile of set.profiles) {
|
|
93
|
+
const value = slug(profile.modelConfigIdentity)
|
|
94
|
+
safeSegment(value)
|
|
95
|
+
if (profileSlugs.has(value)) throw new Error('instruction_set_slug_collision')
|
|
96
|
+
profileSlugs.add(value)
|
|
97
|
+
}
|
|
98
|
+
mkdirSync(out, { recursive: true })
|
|
99
|
+
const destination = join(out, set.name)
|
|
100
|
+
if (existsSync(destination) && !existingDestinationIsSynced(destination, set.name)) throw new Error('instruction_set_sync_destination_not_synced')
|
|
101
|
+
const temp = join(out, `.${set.name}.${randomUUID()}.tmp`)
|
|
102
|
+
const move = fileOps.renameSync || renameSync
|
|
103
|
+
let backup: string | null = null
|
|
104
|
+
mkdirSync(temp)
|
|
105
|
+
try {
|
|
106
|
+
const defaultMaterial = writeMaterial(temp, set.default, set.shape, 'default', fileOps)
|
|
107
|
+
const profiles = set.profiles.map(profile => ({
|
|
108
|
+
modelConfigIdentity: profile.modelConfigIdentity, slug: slug(profile.modelConfigIdentity), resolvedFrom: profile.resolvedFrom,
|
|
109
|
+
production: profile.production ? writeMaterial(temp, profile.production, set.shape, `profiles/${slug(profile.modelConfigIdentity)}`, fileOps) : null,
|
|
110
|
+
})).sort((a, b) => a.modelConfigIdentity.localeCompare(b.modelConfigIdentity))
|
|
111
|
+
const components = [
|
|
112
|
+
...Object.entries(defaultMaterial.files).map(([key, path]) => ({ key, path, syncedContentSha256: defaultMaterial.syncedContentSha256[key] })),
|
|
113
|
+
...profiles.flatMap(profile => profile.production
|
|
114
|
+
? Object.entries(profile.production.files).map(([key, path]) => ({ key, modelConfig: profile.modelConfigIdentity, path, syncedContentSha256: profile.production!.syncedContentSha256[key] }))
|
|
115
|
+
: []),
|
|
116
|
+
]
|
|
117
|
+
const manifest = { manifestVersion: 1, name: set.name, ...(Object.hasOwn(set, 'description') ? { description: set.description ?? null } : {}), shape: set.shape, components, default: defaultMaterial, profiles, ...(set.filteredTo ? { filteredTo: [...set.filteredTo].sort() } : {}) }
|
|
118
|
+
fileOps.writeFileSync(join(temp, 'manifest.json'), stable(manifest))
|
|
119
|
+
backup = join(out, `.${set.name}.${randomUUID()}.bak`)
|
|
120
|
+
if (existsSync(destination)) move(destination, backup)
|
|
121
|
+
try { move(temp, destination) } catch (error) { if (existsSync(backup)) move(backup, destination); throw error }
|
|
122
|
+
removeOwnScratch([backup])
|
|
123
|
+
} catch (error) {
|
|
124
|
+
removeOwnScratch([temp])
|
|
125
|
+
if (backup && existsSync(backup) && !existsSync(destination)) move(backup, destination)
|
|
126
|
+
removeOwnScratch([backup])
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function instructionSetsCommand(args: string[], io: InstructionSetsCommandIo): Promise<number> {
|
|
132
|
+
const positionals = positionalArgs(args)
|
|
133
|
+
const [subcommand, reference] = positionals
|
|
134
|
+
if (subcommand === 'default' && reference === 'move') {
|
|
135
|
+
const version = argValue(args, '--version')
|
|
136
|
+
if (!version || !/^[0-9]+$/u.test(version) || Number(version) < 1) throw new Error('--version must be a positive integer')
|
|
137
|
+
}
|
|
138
|
+
let writeManifest: InstructionSetManifest | undefined
|
|
139
|
+
if (subcommand === 'create' || subcommand === 'push') {
|
|
140
|
+
if (!reference || reference.startsWith('--')) throw new Error(subcommand === 'create'
|
|
141
|
+
? 'Usage: orizu instructions create <manifest> --project <team/project> [--runner-version <id>] [--model-config <identity>] [--json]'
|
|
142
|
+
: 'Usage: orizu instructions push <manifest> --project <team/project> [--set <slug-or-exact-name>] [--runner-version <id>] [--json]')
|
|
143
|
+
writeManifest = loadInstructionSetManifest(reference)
|
|
144
|
+
}
|
|
145
|
+
const project = await io.resolveProjectSlug(argValue(args, '--project'))
|
|
146
|
+
if (subcommand === 'default') {
|
|
147
|
+
const operation = reference; const set = positionals[2]; const identity = argValue(args, '--model-config'); const version = argValue(args, '--version')
|
|
148
|
+
if ((operation !== 'show' && operation !== 'move') || !set) throw new Error('Usage: orizu instructions default show|move <set> --project <team/project> [--model-config <identity> --version <n>] [--json]')
|
|
149
|
+
if (operation === 'move' && (!identity || !version || !/^[0-9]+$/u.test(version) || Number(version) < 1)) throw new Error('--version must be a positive integer')
|
|
150
|
+
const path = `/api/cli/instruction-sets/${encodeURIComponent(set)}/default?project=${encodeURIComponent(project)}`
|
|
151
|
+
if (operation === 'show') {
|
|
152
|
+
const payload = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets default show')
|
|
153
|
+
if (io.json) io.print(JSON.stringify(payload)); else io.print(`Default: v${(payload.default as { versionNumber?: number } | undefined)?.versionNumber ?? '?'}`)
|
|
154
|
+
return 0
|
|
155
|
+
}
|
|
156
|
+
if (!io.json) {
|
|
157
|
+
const impact = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets default show')
|
|
158
|
+
const resolvesToDefault = Array.isArray(impact.resolvesToDefault) ? impact.resolvesToDefault.filter((item): item is string => typeof item === 'string') : []
|
|
159
|
+
io.print(`These model configs resolve to the default: ${resolvesToDefault.join(', ')}`)
|
|
160
|
+
}
|
|
161
|
+
const payload = await responsePayload(await authedFetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modelConfigIdentity: identity, versionNumber: Number(version) }) }), 'Instruction sets default move')
|
|
162
|
+
if (io.json) io.print(JSON.stringify(payload)); else io.print(`Default moved to ${identity} v${version}`)
|
|
163
|
+
return 0
|
|
164
|
+
}
|
|
165
|
+
if (subcommand === 'shape') {
|
|
166
|
+
const operation = reference; const set = positionals[2]; const key = argValue(args, '--key'); const from = argValue(args, '--from')
|
|
167
|
+
if ((operation !== 'add' && operation !== 'remove') || !set || !key || (operation === 'add' && !from)) throw new Error('Usage: orizu instructions shape add|remove <set> --project <team/project> --key <key> [--from <manifest>] [--json]')
|
|
168
|
+
let component: { body: string } | undefined
|
|
169
|
+
if (operation === 'add') {
|
|
170
|
+
let manifest: unknown
|
|
171
|
+
try { manifest = JSON.parse(readFileSync(from!, 'utf8')) } catch { throw new Error('instruction_set_manifest_invalid_json') }
|
|
172
|
+
const source = manifest && typeof manifest === 'object' && Array.isArray((manifest as { components?: unknown }).components)
|
|
173
|
+
? (manifest as { components: unknown[] }).components.find((item): item is { key?: unknown; text?: unknown } => Boolean(item && typeof item === 'object' && (item as { key?: unknown }).key === key))
|
|
174
|
+
: undefined
|
|
175
|
+
if (!source || typeof source.text !== 'string' || Buffer.byteLength(source.text, 'utf8') > MAX_INSTRUCTION_SET_COMPONENT_BYTES) throw new Error(`Instruction set manifest has no component for key: ${key}`)
|
|
176
|
+
component = { body: source.text }
|
|
177
|
+
}
|
|
178
|
+
const payload = await responsePayload(await authedFetch(`/api/cli/instruction-sets/${encodeURIComponent(set)}/shape?project=${encodeURIComponent(project)}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ operation, key, ...(component ? { component } : {}) }) }), `Instruction sets shape ${operation}`)
|
|
179
|
+
if (io.json) io.print(JSON.stringify(payload)); else {
|
|
180
|
+
io.print(`Shape ${operation === 'add' ? 'added' : 'removed'}: ${key}`)
|
|
181
|
+
const affected = Array.isArray(payload.affected) ? payload.affected : []
|
|
182
|
+
const identities = [...new Set(affected.flatMap(pointer => pointer && typeof pointer === 'object' && typeof (pointer as { modelConfigIdentity?: unknown }).modelConfigIdentity === 'string' ? [(pointer as { modelConfigIdentity: string }).modelConfigIdentity] : []))]
|
|
183
|
+
if (identities.length) io.print(`${set} does not resolve for these model configs until the pointers move: ${identities.join(', ')}`)
|
|
184
|
+
for (const pointer of affected) if (pointer && typeof pointer === 'object') {
|
|
185
|
+
const item = pointer as { modelConfigIdentity?: unknown; pointer?: unknown; stalePointerVersionNumber?: unknown; headVersionNumber?: unknown; branchedFromVersionNumber?: unknown }
|
|
186
|
+
if (typeof item.modelConfigIdentity === 'string' && (item.pointer === 'production' || item.pointer === 'default') && typeof item.headVersionNumber === 'number') {
|
|
187
|
+
const command = item.pointer === 'production' ? 'profiles promote' : 'default move'
|
|
188
|
+
const operatorAction = item.pointer === 'production' ? 'promote' : 'default move'
|
|
189
|
+
if (typeof item.stalePointerVersionNumber === 'number' && typeof item.branchedFromVersionNumber === 'number' && item.branchedFromVersionNumber !== item.stalePointerVersionNumber) io.print(`${operatorAction} would move ${item.pointer} from v${item.stalePointerVersionNumber}'s text to v${item.headVersionNumber} = v${item.branchedFromVersionNumber}'s text + ${key}`)
|
|
190
|
+
io.print(`Follow up: instructions ${command} ${set} --project ${project} --model-config ${item.modelConfigIdentity} --version ${item.headVersionNumber}`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return 0
|
|
195
|
+
}
|
|
196
|
+
if (subcommand === 'profiles') {
|
|
197
|
+
const operation = reference; const set = positionals[2]; const identity = argValue(args, '--model-config')
|
|
198
|
+
if ((operation !== 'new' && operation !== 'promote' && operation !== 'rollback') || !set || !identity) throw new Error('Usage: orizu instructions profiles new|promote|rollback <set> --project <team/project> --model-config <identity> [--version <n>|--to <n>] [--json]')
|
|
199
|
+
const encodedSet = encodeURIComponent(set); const encodedIdentity = encodeURIComponent(identity)
|
|
200
|
+
const version = argValue(args, '--version'); const to = argValue(args, '--to')
|
|
201
|
+
if ((operation === 'promote' && (!version || !/^[0-9]+$/u.test(version) || Number(version) < 1)) || (operation === 'rollback' && (!to || !/^[0-9]+$/u.test(to) || Number(to) < 1))) throw new Error(operation === 'promote' ? '--version must be a positive integer' : '--to must be a positive integer')
|
|
202
|
+
const path = operation === 'new' ? `/api/cli/instruction-sets/${encodedSet}/profiles` : operation === 'promote' ? `/api/cli/instruction-sets/${encodedSet}/profiles/${encodedIdentity}/label` : `/api/cli/instruction-sets/${encodedSet}/profiles/${encodedIdentity}/rollback`
|
|
203
|
+
const body = operation === 'new' ? { modelConfigIdentity: identity } : operation === 'promote' ? { versionNumber: Number(version) } : { to: Number(to) }
|
|
204
|
+
const payload = await responsePayload(await authedFetch(`${path}?project=${encodeURIComponent(project)}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }), `Instruction sets profiles ${operation}`)
|
|
205
|
+
if (io.json) io.print(JSON.stringify(payload))
|
|
206
|
+
else {
|
|
207
|
+
const profile = payload.profile as { modelConfigIdentity?: string; versionNumber?: number } | undefined
|
|
208
|
+
io.print(`${profile?.modelConfigIdentity || identity}: v${profile?.versionNumber ?? '?'} ${operation === 'new' ? 'created' : operation === 'promote' ? 'promoted' : 'rolled back'}`)
|
|
209
|
+
const mirrored = payload.mirroredPromptLabel as { promptName?: unknown; promptId?: unknown } | undefined
|
|
210
|
+
if (mirrored && (typeof mirrored.promptName === 'string' || typeof mirrored.promptId === 'string')) io.print(`also moved production for prompt ${typeof mirrored.promptName === 'string' ? mirrored.promptName : mirrored.promptId} (default profile → prompt label)`)
|
|
211
|
+
}
|
|
212
|
+
return 0
|
|
213
|
+
}
|
|
214
|
+
if (subcommand === 'create' || subcommand === 'push') {
|
|
215
|
+
const manifest = writeManifest!; const modelConfig = argValue(args, '--model-config'); const runnerVersion = argValue(args, '--runner-version')
|
|
216
|
+
const setReference = argValue(args, '--set') || manifest.name
|
|
217
|
+
const path = subcommand === 'create' ? '/api/cli/instruction-sets' : `/api/cli/instruction-sets/${encodeURIComponent(setReference)}/versions`
|
|
218
|
+
const body = subcommand === 'create'
|
|
219
|
+
? { ...manifest, ...(modelConfig ? { modelConfigIdentity: modelConfig } : {}), ...(runnerVersion ? { runnerVersionId: runnerVersion } : {}) }
|
|
220
|
+
: { shape: manifest.shape, components: manifest.components, ...(Object.hasOwn(manifest, 'description') ? { description: manifest.description } : {}), ...(runnerVersion ? { runnerVersionId: runnerVersion } : {}) }
|
|
221
|
+
const payload = await responsePayload(await authedFetch(`${path}?project=${encodeURIComponent(project)}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }), `Instruction sets ${subcommand}`)
|
|
222
|
+
if (io.json) io.print(JSON.stringify(payload))
|
|
223
|
+
else {
|
|
224
|
+
const set = payload.instructionSet as { name?: string; profiles?: unknown[] } | undefined
|
|
225
|
+
const pushed = Array.isArray(payload.pushed) ? payload.pushed : []
|
|
226
|
+
const unchanged = Array.isArray(payload.unchanged) ? payload.unchanged : []
|
|
227
|
+
const skipped = Array.isArray(payload.skipped) ? payload.skipped : []
|
|
228
|
+
if (subcommand === 'create') {
|
|
229
|
+
const profiles = Array.isArray(set?.profiles) ? set.profiles : []
|
|
230
|
+
io.print(`${set?.name || manifest.name}: ${profiles.length} profile(s) created`)
|
|
231
|
+
} else {
|
|
232
|
+
const describe = (value: unknown) => {
|
|
233
|
+
if (typeof value === 'string') return value
|
|
234
|
+
if (value && typeof value === 'object') {
|
|
235
|
+
const row = value as { modelConfigIdentity?: unknown; versionNumber?: unknown }
|
|
236
|
+
return `${typeof row.modelConfigIdentity === 'string' ? row.modelConfigIdentity : 'unknown'}${typeof row.versionNumber === 'number' ? ` v${row.versionNumber}` : ''}`
|
|
237
|
+
}
|
|
238
|
+
return 'unknown'
|
|
239
|
+
}
|
|
240
|
+
io.print(`${set?.name || manifest.name}: pushed ${pushed.map(describe).join(', ') || 'none'}; unchanged ${unchanged.map(describe).join(', ') || 'none'}; skipped ${skipped.map(describe).join(', ') || 'none'}`)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return 0
|
|
244
|
+
}
|
|
245
|
+
if (subcommand === 'archive' || subcommand === 'restore') {
|
|
246
|
+
if (!reference || reference.startsWith('--')) throw new Error(`Usage: orizu instructions ${subcommand} <slug-or-exact-name> --project <team/project> [--json]`)
|
|
247
|
+
const archived = subcommand === 'archive'
|
|
248
|
+
const payload = await responsePayload(await authedFetch(`/api/cli/instruction-sets/${encodeURIComponent(reference)}?project=${encodeURIComponent(project)}`, {
|
|
249
|
+
method: 'PATCH',
|
|
250
|
+
headers: { 'content-type': 'application/json' },
|
|
251
|
+
body: JSON.stringify({ archived }),
|
|
252
|
+
}), `Instructions ${subcommand}`)
|
|
253
|
+
if (io.json) io.print(JSON.stringify(payload))
|
|
254
|
+
else {
|
|
255
|
+
const set = payload.instructionSet as { name?: string; slug?: string } | undefined
|
|
256
|
+
const identity = set?.slug && set.slug !== set.name ? `${set.name || reference} (${set.slug})` : set?.name || set?.slug || reference
|
|
257
|
+
io.print(`${archived ? 'Archived' : 'Restored'} instruction set ${identity}`)
|
|
258
|
+
}
|
|
259
|
+
return 0
|
|
260
|
+
}
|
|
261
|
+
const status = argValue(args, '--status') || 'active'
|
|
262
|
+
if (subcommand !== 'list' && subcommand !== 'show' && subcommand !== 'sync') throw new Error('Usage: orizu instructions list|show|sync|archive|restore')
|
|
263
|
+
if (subcommand === 'sync') {
|
|
264
|
+
const output = argValue(args, '--out')
|
|
265
|
+
if (!reference || !output) throw new Error('Usage: orizu instructions sync <set> --out <dir> --project <team/project> [--model-config <identity>] [--json]')
|
|
266
|
+
const modelConfig = argValue(args, '--model-config')
|
|
267
|
+
const path = `/api/cli/instruction-sets/${encodeURIComponent(reference)}/sync?project=${encodeURIComponent(project)}${modelConfig ? `&modelConfig=${encodeURIComponent(modelConfig)}` : ''}`
|
|
268
|
+
const payload = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets sync')
|
|
269
|
+
const set = payload.instructionSet as SyncSet
|
|
270
|
+
if (!set || (set.slug !== reference && set.name !== reference) || (modelConfig && (!Array.isArray(set.filteredTo) || set.filteredTo.length !== 1 || set.filteredTo[0] !== modelConfig))) {
|
|
271
|
+
throw new Error('instruction_set_sync_response_mismatch')
|
|
272
|
+
}
|
|
273
|
+
syncToDisk(output, set)
|
|
274
|
+
if (io.json) io.print(JSON.stringify(payload)); else io.print(`Synced ${set.name} to ${join(output, set.name)}`)
|
|
275
|
+
return 0
|
|
276
|
+
}
|
|
277
|
+
if (subcommand === 'show' && (!reference || reference.startsWith('--'))) throw new Error('Instruction set name is required')
|
|
278
|
+
if (status !== 'active' && status !== 'archived' && status !== 'all') throw new Error('--status must be active, archived, or all')
|
|
279
|
+
|
|
280
|
+
const query = `project=${encodeURIComponent(project)}&status=${encodeURIComponent(status)}`
|
|
281
|
+
const path = subcommand === 'list'
|
|
282
|
+
? `/api/cli/instruction-sets?${query}`
|
|
283
|
+
: `/api/cli/instruction-sets/${encodeURIComponent(reference!)}?${query}`
|
|
284
|
+
const payload = await responsePayload(await authedFetch(path, { method: 'GET' }), `Instruction sets ${subcommand}`)
|
|
285
|
+
if (io.json) {
|
|
286
|
+
io.print(JSON.stringify(payload))
|
|
287
|
+
return 0
|
|
288
|
+
}
|
|
289
|
+
if (subcommand === 'list') {
|
|
290
|
+
const sets = Array.isArray(payload.instructionSets) ? payload.instructionSets : []
|
|
291
|
+
for (const item of sets) {
|
|
292
|
+
const set = item as { name?: string; slug?: string; description?: string | null; shape?: string[]; status?: string }
|
|
293
|
+
io.print(`${set.name || 'unnamed'}${set.slug ? ` [${set.slug}]` : ''}${set.status === 'archived' ? ' [archived]' : ''}${set.shape ? ` (${set.shape.join(', ')})` : ''}`)
|
|
294
|
+
if (set.description) io.print(` ${sanitizeHumanInlineText(sanitizeTerminalText, set.description)}`)
|
|
295
|
+
}
|
|
296
|
+
return 0
|
|
297
|
+
}
|
|
298
|
+
const set = payload.instructionSet as { name?: string; description?: string | null; shape?: string[]; status?: string; default?: { versionNumber?: number } | null; profiles?: Array<{ modelConfigIdentity?: string | null; production?: { versionNumber?: number } | null; latestVersionNumber?: number | null }> } | undefined
|
|
299
|
+
io.print(`${set?.name || reference}${set?.status === 'archived' ? ' [archived]' : ''}: ${set?.shape?.join(', ') || ''}`)
|
|
300
|
+
if (set?.description) io.print(`Description: ${sanitizeHumanInlineText(sanitizeTerminalText, set.description)}`)
|
|
301
|
+
if (set?.default) io.print(`Default: v${set.default.versionNumber ?? '?'}`)
|
|
302
|
+
for (const profile of set?.profiles || []) {
|
|
303
|
+
io.print(`${profile.modelConfigIdentity || 'unspecified'}: production ${profile.production ? `v${profile.production.versionNumber}` : '—'}, latest ${profile.latestVersionNumber ?? '—'}`)
|
|
304
|
+
}
|
|
305
|
+
return 0
|
|
306
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import { authedFetch } from './http.js'
|
|
4
|
+
|
|
5
|
+
export type CliFetcher = (path: string, init?: RequestInit) => Promise<Response>
|
|
6
|
+
|
|
7
|
+
export interface ModelConfigSettingsVersion {
|
|
8
|
+
versionNumber: number
|
|
9
|
+
settings: Record<string, unknown>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ModelConfig {
|
|
13
|
+
identity: string
|
|
14
|
+
displayName: string
|
|
15
|
+
currentSettingsVersion: ModelConfigSettingsVersion
|
|
16
|
+
createdAt: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ModelConfigsCommandIo {
|
|
20
|
+
json: boolean
|
|
21
|
+
print: (line: string) => void
|
|
22
|
+
fetcher?: CliFetcher
|
|
23
|
+
resolveProjectSlug?: (arg: string | null) => Promise<string>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function argValue(args: string[], flag: string): string | null {
|
|
27
|
+
const index = args.lastIndexOf(flag)
|
|
28
|
+
return index === -1 || !args[index + 1] || args[index + 1]!.startsWith('--') ? null : args[index + 1]!
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function positionalArgs(args: string[]): string[] {
|
|
32
|
+
const values: string[] = []
|
|
33
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
34
|
+
if (args[index]!.startsWith('--')) {
|
|
35
|
+
if (args[index + 1] && !args[index + 1]!.startsWith('--')) index += 1
|
|
36
|
+
} else values.push(args[index]!)
|
|
37
|
+
}
|
|
38
|
+
return values
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readSettings(value: string | null, defaultToEmpty = false): Record<string, unknown> {
|
|
42
|
+
if (!value) {
|
|
43
|
+
if (defaultToEmpty) return {}
|
|
44
|
+
throw new Error('--settings is required')
|
|
45
|
+
}
|
|
46
|
+
const source = value.startsWith('@') ? readFileSync(value.slice(1), 'utf8') : value
|
|
47
|
+
const parsed: unknown = JSON.parse(source)
|
|
48
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
49
|
+
throw new Error('--settings must contain a JSON object')
|
|
50
|
+
}
|
|
51
|
+
return parsed as Record<string, unknown>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function responseMessage(response: Response): Promise<string> {
|
|
55
|
+
try {
|
|
56
|
+
const body = await response.clone().json() as Record<string, unknown>
|
|
57
|
+
return typeof body.error === 'string' ? body.error : response.statusText
|
|
58
|
+
} catch {
|
|
59
|
+
return response.statusText
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function requestJson(fetcher: CliFetcher, path: string, init?: RequestInit): Promise<unknown> {
|
|
64
|
+
const response = await fetcher(path, init)
|
|
65
|
+
if (!response.ok) throw new Error(`Model config request failed (${response.status}): ${await responseMessage(response)}`)
|
|
66
|
+
return response.json()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderModelConfig(config: ModelConfig): string {
|
|
70
|
+
return `${config.identity} ${config.displayName} v${config.currentSettingsVersion.versionNumber}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function modelConfigsCommand(args: string[], io: ModelConfigsCommandIo): Promise<number> {
|
|
74
|
+
const positional = positionalArgs(args)
|
|
75
|
+
const [command, action, identity] = positional
|
|
76
|
+
const resolveProjectSlug = io.resolveProjectSlug
|
|
77
|
+
if (!resolveProjectSlug) throw new Error('Project resolver unavailable')
|
|
78
|
+
const projectArg = argValue(args, '--project')
|
|
79
|
+
const project = projectArg || await resolveProjectSlug(null)
|
|
80
|
+
const fetcher = io.fetcher ?? authedFetch
|
|
81
|
+
const root = `/api/cli/model-configs?project=${encodeURIComponent(project)}`
|
|
82
|
+
let result: unknown
|
|
83
|
+
|
|
84
|
+
if (command === 'list' && !action) {
|
|
85
|
+
result = await requestJson(fetcher, root, { method: 'GET' })
|
|
86
|
+
if (io.json) io.print(JSON.stringify(result))
|
|
87
|
+
else for (const config of ((result as { modelConfigs?: ModelConfig[] }).modelConfigs || [])) io.print(renderModelConfig(config))
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
if (command === 'show' && action && !identity) {
|
|
91
|
+
result = await requestJson(fetcher, `/api/cli/model-configs/${encodeURIComponent(action)}?project=${encodeURIComponent(project)}`, { method: 'GET' })
|
|
92
|
+
} else if (command === 'create' && action && !identity) {
|
|
93
|
+
result = await requestJson(fetcher, root, {
|
|
94
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
95
|
+
body: JSON.stringify({ identity: action, displayName: argValue(args, '--name') || action, settings: readSettings(argValue(args, '--settings'), true) }),
|
|
96
|
+
})
|
|
97
|
+
} else if (command === 'settings' && action === 'set' && identity) {
|
|
98
|
+
result = await requestJson(fetcher, `/api/cli/model-configs/${encodeURIComponent(identity)}/settings?project=${encodeURIComponent(project)}`, {
|
|
99
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ settings: readSettings(argValue(args, '--settings')) }),
|
|
100
|
+
})
|
|
101
|
+
} else if (command === 'copy' && action && !identity) {
|
|
102
|
+
const to = argValue(args, '--to')
|
|
103
|
+
if (!to) throw new Error('--to is required')
|
|
104
|
+
result = await requestJson(fetcher, `/api/cli/model-configs/${encodeURIComponent(action)}/copy?project=${encodeURIComponent(project)}`, {
|
|
105
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ to }),
|
|
106
|
+
})
|
|
107
|
+
} else {
|
|
108
|
+
io.print('Usage: orizu model-configs <create|list|show|settings set|copy> [--project <team/project>] [--json]')
|
|
109
|
+
return 1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (io.json) {
|
|
113
|
+
io.print(JSON.stringify(result))
|
|
114
|
+
} else {
|
|
115
|
+
const config = (result as { modelConfig: ModelConfig }).modelConfig
|
|
116
|
+
io.print(renderModelConfig(config))
|
|
117
|
+
}
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { extractErrorMessage } from './error-response.js'
|
|
2
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
3
|
+
|
|
4
|
+
export interface OptimizationsPromoteIo {
|
|
5
|
+
json: boolean
|
|
6
|
+
print: (line: string) => void
|
|
7
|
+
resolveProjectSlug: (projectArg: string | null) => Promise<string>
|
|
8
|
+
fetcher: (path: string, init?: RequestInit) => Promise<Response>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function option(args: readonly string[], name: string): string | null {
|
|
12
|
+
const index = args.indexOf(name)
|
|
13
|
+
return index === -1 || !args[index + 1] || args[index + 1].startsWith('--') ? null : args[index + 1]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function promoteOptimizationCommand(args: string[], io: OptimizationsPromoteIo): Promise<void> {
|
|
17
|
+
const runId = args.find(arg => !arg.startsWith('--') && arg !== option(args, '--candidate') && arg !== option(args, '--label') && arg !== option(args, '--project'))
|
|
18
|
+
const candidateId = option(args, '--candidate')
|
|
19
|
+
const label = option(args, '--label')
|
|
20
|
+
if (!runId || !candidateId || (label !== null && label !== 'production')) {
|
|
21
|
+
throw new Error('Usage: orizu optimizations promote <run-id> --candidate <id> [--label production] [--project <team/project>] [--json]')
|
|
22
|
+
}
|
|
23
|
+
const project = await io.resolveProjectSlug(option(args, '--project'))
|
|
24
|
+
const response = await io.fetcher(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/promote?project=${encodeURIComponent(project)}`, {
|
|
25
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ candidateId, label }),
|
|
26
|
+
})
|
|
27
|
+
if (!response.ok) throw new Error(`Failed to promote optimization candidate: ${await extractErrorMessage(response)}`)
|
|
28
|
+
const data = await response.json() as { promptVersionId?: string; profileVersionId?: string; components?: Array<{ key: string; status: string }> }
|
|
29
|
+
if (io.json) {
|
|
30
|
+
io.print(JSON.stringify(data.profileVersionId
|
|
31
|
+
? { profileVersionId: data.profileVersionId, components: data.components ?? [] }
|
|
32
|
+
: { promptVersionId: data.promptVersionId }))
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (data.profileVersionId) {
|
|
36
|
+
io.print(`Promoted profile version ${sanitizeTerminalText(data.profileVersionId)}`)
|
|
37
|
+
for (const component of data.components ?? []) io.print(`${sanitizeTerminalText(component.key)}: ${sanitizeTerminalText(component.status)}`)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
io.print(`Promoted prompt version ${sanitizeTerminalText(data.promptVersionId ?? '')}`)
|
|
41
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { authedFetch } from './http.js'
|
|
2
|
+
import { parseJsonResponse, sanitizeTerminalText } from './json-response.js'
|
|
3
|
+
|
|
4
|
+
interface PromptLabelCliContext {
|
|
5
|
+
getArg: (name: string) => string | null
|
|
6
|
+
getPositionalArg: (index: number) => string | null
|
|
7
|
+
json: boolean
|
|
8
|
+
printJson: (value: Record<string, unknown>) => void
|
|
9
|
+
printLine: (value: string) => void
|
|
10
|
+
resolveProjectSlug: (project: string | null) => Promise<string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface PromptLabelResponse {
|
|
14
|
+
prompt_id: string
|
|
15
|
+
prompt_version_id: string
|
|
16
|
+
label: string
|
|
17
|
+
owner?: {
|
|
18
|
+
instructionSetSlug: string
|
|
19
|
+
componentKey: string
|
|
20
|
+
} | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function setPromptLabel(context: PromptLabelCliContext): Promise<void> {
|
|
24
|
+
const promptName = context.getPositionalArg(3)
|
|
25
|
+
const label = context.getPositionalArg(4)
|
|
26
|
+
const project = context.getArg('--project') || await context.resolveProjectSlug(null)
|
|
27
|
+
const promptVersionId = context.getArg('--version')
|
|
28
|
+
|
|
29
|
+
if (!promptName || !label || !promptVersionId) {
|
|
30
|
+
throw new Error('Usage: orizu prompts labels set <prompt-name> <label> --version <prompt-version-id> [--project <team/project>] [--json]')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const response = await authedFetch(`/api/cli/prompts/labels?project=${encodeURIComponent(project)}`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify({ promptName, label, promptVersionId }),
|
|
37
|
+
})
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Failed to set prompt label: ${await response.text()}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = await parseJsonResponse<PromptLabelResponse>(response, 'Prompt label set')
|
|
43
|
+
if (context.json) {
|
|
44
|
+
context.printJson(data as unknown as Record<string, unknown>)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
context.printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`)
|
|
49
|
+
if (data.owner) {
|
|
50
|
+
context.printLine(
|
|
51
|
+
`Owner: ${sanitizeTerminalText(promptName)} (${sanitizeTerminalText(data.prompt_id)}) -> ` +
|
|
52
|
+
`${sanitizeTerminalText(data.owner.instructionSetSlug)} / ${sanitizeTerminalText(data.owner.componentKey)}`
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -10,6 +10,10 @@ interface PromptSummaryRow {
|
|
|
10
10
|
role: string
|
|
11
11
|
status?: string
|
|
12
12
|
lengthStats?: CliLengthStats | null
|
|
13
|
+
owner?: {
|
|
14
|
+
instructionSetSlug: string
|
|
15
|
+
componentKey: string
|
|
16
|
+
} | null
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export function printPromptSummaryTable(
|
|
@@ -54,4 +58,12 @@ export function printPromptSummaryTable(
|
|
|
54
58
|
rows.forEach(row => {
|
|
55
59
|
printLine(keys.map((key, index) => row[key].padEnd(widths[index])).join(' '))
|
|
56
60
|
})
|
|
61
|
+
items.forEach(item => {
|
|
62
|
+
if (item.owner) {
|
|
63
|
+
printLine(
|
|
64
|
+
`Owner: ${sanitizeTerminalText(item.name)} (${sanitizeTerminalText(item.id)}) -> ` +
|
|
65
|
+
`${sanitizeTerminalText(item.owner.instructionSetSlug)} / ${sanitizeTerminalText(item.owner.componentKey)}`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
57
69
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type SyncComponent, type SyncSet } from './instruction-sets-cli.js'
|
|
2
|
+
|
|
3
|
+
export interface RunnerInstructionSet {
|
|
4
|
+
name: string
|
|
5
|
+
modelConfig: Record<string, unknown>
|
|
6
|
+
shape: string[]
|
|
7
|
+
profileVersionId: string
|
|
8
|
+
modelConfigSettingsVersionId?: string
|
|
9
|
+
versionNumber: number
|
|
10
|
+
promptComponentKey?: string
|
|
11
|
+
components: Record<string, string>
|
|
12
|
+
pinnedComponents?: Record<string, { repoPath: string; contentSha: string; commitSha: string }>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function runnerInstructionSetSyncSet(instructionSet: RunnerInstructionSet): SyncSet {
|
|
16
|
+
const modelConfigIdentity = typeof instructionSet.modelConfig.identity === 'string'
|
|
17
|
+
? instructionSet.modelConfig.identity
|
|
18
|
+
: ''
|
|
19
|
+
if (!modelConfigIdentity) throw new Error('Runner exec context instruction set has no model config identity')
|
|
20
|
+
const missingKey = instructionSet.shape.find(key => !(key in instructionSet.components) && !(key in (instructionSet.pinnedComponents || {})))
|
|
21
|
+
if (missingKey) throw new Error('instruction_set_tuple_incomplete')
|
|
22
|
+
const components: Record<string, SyncComponent> = Object.fromEntries(Object.entries(instructionSet.components).map(([key, body]) => [key, { body }]))
|
|
23
|
+
for (const [key, pin] of Object.entries(instructionSet.pinnedComponents || {})) components[key] = pin
|
|
24
|
+
const material = {
|
|
25
|
+
profileVersionId: instructionSet.profileVersionId,
|
|
26
|
+
versionNumber: instructionSet.versionNumber,
|
|
27
|
+
modelConfigIdentity,
|
|
28
|
+
// syncToDisk's long-lived manifest requires provenance; this execution
|
|
29
|
+
// path has no resolver decision to expose, only an exact profile version.
|
|
30
|
+
resolvedFrom: 'exact_profile_version',
|
|
31
|
+
components,
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
name: instructionSet.name,
|
|
35
|
+
shape: instructionSet.shape,
|
|
36
|
+
default: material,
|
|
37
|
+
profiles: [{ modelConfigIdentity, resolvedFrom: 'exact_profile_version', production: material }],
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function runnerInputPrompt(
|
|
42
|
+
promptContext: { body?: string | null; bodyKind: string; providerSettings: Record<string, unknown> }
|
|
43
|
+
): Record<string, unknown> {
|
|
44
|
+
const prompt = {
|
|
45
|
+
body_kind: promptContext.bodyKind,
|
|
46
|
+
provider_settings: promptContext.providerSettings,
|
|
47
|
+
} as Record<string, unknown>
|
|
48
|
+
// The route owns this decision. Preserve its response shape exactly so an
|
|
49
|
+
// implicit wrapped prompt remains compatible with existing runners.
|
|
50
|
+
if ('body' in promptContext) prompt.body = promptContext.body
|
|
51
|
+
return prompt
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function runnerInputInstructionSet(instructionSet: RunnerInstructionSet): Record<string, unknown> {
|
|
55
|
+
return {
|
|
56
|
+
name: instructionSet.name,
|
|
57
|
+
model_config: instructionSet.modelConfig,
|
|
58
|
+
shape: instructionSet.shape,
|
|
59
|
+
...(instructionSet.modelConfigSettingsVersionId ? { model_config_settings_version_id: instructionSet.modelConfigSettingsVersionId } : {}),
|
|
60
|
+
...(instructionSet.promptComponentKey ? { prompt_component_key: instructionSet.promptComponentKey } : {}),
|
|
61
|
+
components: instructionSet.components,
|
|
62
|
+
...(Object.keys(instructionSet.pinnedComponents || {}).length > 0 ? { pinned_components: instructionSet.pinnedComponents } : {}),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `orizu scorers` subcommand router (ALI-1554).
|
|
3
|
+
*
|
|
4
|
+
* The five historical branches lived inline in index.ts; folding them here
|
|
5
|
+
* reclaims the headroom the CLI line ratchet (ALI-976) requires before a new
|
|
6
|
+
* subcommand may be added. Behaviour is unchanged: each handler is the same
|
|
7
|
+
* function index.ts already called, and an unrecognised subcommand returns
|
|
8
|
+
* null so main() still falls through to printUsage().
|
|
9
|
+
*/
|
|
10
|
+
export interface ScorersCommandIo {
|
|
11
|
+
list: () => Promise<void>
|
|
12
|
+
register: () => Promise<void>
|
|
13
|
+
detail: () => Promise<void>
|
|
14
|
+
labelsSet: () => Promise<void>
|
|
15
|
+
exec: () => Promise<void>
|
|
16
|
+
verifyParity: (args: string[]) => Promise<number>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Returns the process exit code, or null when the subcommand is not ours. */
|
|
20
|
+
export async function scorersCommand(args: string[], io: ScorersCommandIo): Promise<number | null> {
|
|
21
|
+
const subcommand = args[1]
|
|
22
|
+
// A Map, not an object literal: `scorers constructor` must not resolve to
|
|
23
|
+
// an Object.prototype member and be treated as a known subcommand.
|
|
24
|
+
const handlers = new Map<string, () => Promise<void>>([
|
|
25
|
+
['list', io.list], ['register', io.register], ['detail', io.detail], ['exec', io.exec],
|
|
26
|
+
])
|
|
27
|
+
const handler = subcommand === undefined ? undefined : handlers.get(subcommand)
|
|
28
|
+
if (handler) {
|
|
29
|
+
await handler()
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
if (subcommand === 'labels' && args[2] === 'set') {
|
|
33
|
+
await io.labelsSet()
|
|
34
|
+
return 0
|
|
35
|
+
}
|
|
36
|
+
if (subcommand === 'verify-parity') {
|
|
37
|
+
return io.verifyParity(args.slice(2))
|
|
38
|
+
}
|
|
39
|
+
return null
|
|
40
|
+
}
|