orizu 0.4.1 → 0.5.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/README.md +22 -13
- package/dist/artifact-archive.js +152 -0
- package/dist/connectors-cli.js +80 -0
- package/dist/credentials.js +4 -1
- package/dist/daytona-slice-rehearsal.js +255 -0
- package/dist/daytona-slice.js +441 -0
- package/dist/egress-allowlist-cli.js +132 -0
- package/dist/egress-policy.js +159 -0
- package/dist/git-credential.js +131 -0
- package/dist/github-setup.js +226 -0
- package/dist/help.js +271 -16
- package/dist/hosted-bootstrap-rehearsal.js +235 -0
- package/dist/hosted-bootstrap.js +365 -0
- package/dist/hosted-broker-server.js +220 -0
- package/dist/hosted-git-http-server.js +136 -0
- package/dist/hosted-harness-claude.js +493 -0
- package/dist/hosted-harness-opencode.js +729 -0
- package/dist/hosted-harness.js +60 -0
- package/dist/hosted-loop.js +477 -0
- package/dist/hosted-run-event-sink.js +499 -0
- package/dist/hosted-runtime-assets.js +216 -0
- package/dist/hosted-session-cli.js +642 -0
- package/dist/index.js +855 -237
- package/dist/kill-switch-cli.js +89 -0
- package/dist/manifests-cli.js +143 -0
- package/dist/preview-runtime.js +4 -1
- package/dist/sandbox-provider.js +238 -0
- package/dist/secret-redaction.js +112 -0
- package/dist/session-timeline.js +103 -0
- package/dist/setup-prompt.js +4 -2
- package/dist/skill-installer.js +9 -1
- package/dist/smoke.js +250 -0
- package/dist/task-assignment-manifest.js +39 -2
- package/dist/vercel-sandbox-provider.js +260 -0
- package/dist/workbench-cli.js +493 -0
- package/dist/workspace-sync.js +924 -0
- package/dist/workspace.js +868 -57
- package/package.json +4 -1
- package/src/artifact-archive.ts +182 -0
- package/src/connectors-cli.ts +115 -0
- package/src/credentials.ts +6 -2
- package/src/daytona-slice-rehearsal.ts +320 -0
- package/src/daytona-slice.ts +598 -0
- package/src/egress-allowlist-cli.ts +147 -0
- package/src/egress-policy.ts +188 -0
- package/src/git-credential.ts +155 -0
- package/src/github-setup.ts +366 -0
- package/src/help.ts +272 -16
- package/src/hosted-bootstrap-rehearsal.ts +312 -0
- package/src/hosted-bootstrap.ts +571 -0
- package/src/hosted-broker-server.ts +278 -0
- package/src/hosted-git-http-server.ts +164 -0
- package/src/hosted-harness-claude.ts +621 -0
- package/src/hosted-harness-opencode.ts +823 -0
- package/src/hosted-harness.ts +124 -0
- package/src/hosted-loop.ts +680 -0
- package/src/hosted-run-event-sink.ts +632 -0
- package/src/hosted-runtime-assets.ts +272 -0
- package/src/hosted-session-cli.ts +844 -0
- package/src/index.ts +987 -296
- package/src/kill-switch-cli.ts +113 -0
- package/src/manifests-cli.ts +168 -0
- package/src/preview-runtime.ts +14 -9
- package/src/sandbox-provider.ts +440 -0
- package/src/secret-redaction.ts +124 -0
- package/src/session-timeline.ts +152 -0
- package/src/setup-prompt.ts +4 -2
- package/src/skill-installer.ts +13 -1
- package/src/smoke.ts +342 -0
- package/src/task-assignment-manifest.ts +70 -2
- package/src/vercel-sandbox-provider.ts +388 -0
- package/src/workbench-cli.ts +701 -0
- package/src/workspace-sync.ts +1148 -0
- package/src/workspace.ts +1051 -64
- package/vendor/skills/orizu-cli/SKILL.md +3 -1
- package/vendor/skills/orizu-cli/references/cli-reference.md +3 -3
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash, randomBytes, randomUUID } from 'crypto'
|
|
3
|
-
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize } from 'path'
|
|
3
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from 'path'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import { createServer } from 'http'
|
|
6
6
|
import { gunzipSync, gzipSync } from 'zlib'
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
existsSync,
|
|
9
9
|
mkdirSync,
|
|
10
10
|
mkdtempSync,
|
|
11
|
-
readdirSync,
|
|
12
11
|
readFileSync,
|
|
13
12
|
realpathSync,
|
|
14
13
|
rmSync,
|
|
@@ -16,8 +15,9 @@ import {
|
|
|
16
15
|
writeFileSync,
|
|
17
16
|
} from 'fs'
|
|
18
17
|
import { spawn, spawnSync } from 'child_process'
|
|
19
|
-
import { tmpdir } from 'os'
|
|
18
|
+
import { homedir, tmpdir } from 'os'
|
|
20
19
|
import { createInterface } from 'readline/promises'
|
|
20
|
+
import { emitKeypressEvents } from 'readline'
|
|
21
21
|
import { stdin as input, stdout as output } from 'process'
|
|
22
22
|
import { clearServerCredentials, getServerCredentials, saveServerCredentials } from './credentials.js'
|
|
23
23
|
import { parseDatasetFile } from './file-parser.js'
|
|
@@ -26,15 +26,10 @@ import { parseDatasetReference } from './dataset-download.js'
|
|
|
26
26
|
import { extractErrorMessage } from './error-response.js'
|
|
27
27
|
import { parseGlobalFlags } from './global-flags.js'
|
|
28
28
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js'
|
|
29
|
+
import { zipDirectoryToBase64 } from './artifact-archive.js'
|
|
29
30
|
import { runLocalAppPreview } from './preview-runtime.js'
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
assertSecureTokenTransport,
|
|
33
|
-
authedFetch,
|
|
34
|
-
getBaseUrl,
|
|
35
|
-
resolveLoginBaseUrl,
|
|
36
|
-
setGlobalFlags,
|
|
37
|
-
} from './http.js'
|
|
31
|
+
import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js'
|
|
32
|
+
import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags } from './http.js'
|
|
38
33
|
import {
|
|
39
34
|
computeSkillContentHash,
|
|
40
35
|
getSkillInstallPath,
|
|
@@ -46,6 +41,7 @@ import {
|
|
|
46
41
|
resolveSkillSource,
|
|
47
42
|
SKILL_INSTALL_AGENTS,
|
|
48
43
|
SKILL_INSTALL_TARGETS,
|
|
44
|
+
SkillInstallAgent,
|
|
49
45
|
SkillInstallMode,
|
|
50
46
|
SkillInstallScope,
|
|
51
47
|
SkillInstallTarget,
|
|
@@ -59,9 +55,26 @@ import { LoginResponse } from './types.js'
|
|
|
59
55
|
import {
|
|
60
56
|
getWorkspaceRoot,
|
|
61
57
|
initOrizuWorkspace,
|
|
62
|
-
|
|
58
|
+
type WorkspaceProjectSeed,
|
|
63
59
|
workspaceExists,
|
|
64
60
|
} from './workspace.js'
|
|
61
|
+
import { connectorsCommand } from './connectors-cli.js'
|
|
62
|
+
import { killSwitchCommand } from './kill-switch-cli.js'
|
|
63
|
+
import { egressAllowlistCommand } from './egress-allowlist-cli.js'
|
|
64
|
+
import { manifestsCommand } from './manifests-cli.js'
|
|
65
|
+
import { workbenchCommand } from './workbench-cli.js'
|
|
66
|
+
import { hostedCommand } from './hosted-session-cli.js'
|
|
67
|
+
import { workspaceSyncCommand } from './workspace-sync.js'
|
|
68
|
+
import { runGitCredential } from './git-credential.js'
|
|
69
|
+
import { type GithubLinkResult, runGithubLink, runInteractiveHostedSetup } from './github-setup.js'
|
|
70
|
+
|
|
71
|
+
function getErrorMessage(error: unknown): string {
|
|
72
|
+
return error instanceof Error ? error.message : String(error)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
76
|
+
return error instanceof Error
|
|
77
|
+
}
|
|
65
78
|
|
|
66
79
|
interface Team {
|
|
67
80
|
id: string
|
|
@@ -279,6 +292,21 @@ interface TaskStatusPayload {
|
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
294
|
|
|
295
|
+
const TASK_STATUS_ACTION_LABELS = {
|
|
296
|
+
paused: {
|
|
297
|
+
verb: 'pause',
|
|
298
|
+
pastTense: 'Paused',
|
|
299
|
+
},
|
|
300
|
+
active: {
|
|
301
|
+
verb: 'unpause',
|
|
302
|
+
pastTense: 'Unpaused',
|
|
303
|
+
},
|
|
304
|
+
completed: {
|
|
305
|
+
verb: 'complete',
|
|
306
|
+
pastTense: 'Completed',
|
|
307
|
+
},
|
|
308
|
+
} as const
|
|
309
|
+
|
|
282
310
|
function getCliVersion(): string {
|
|
283
311
|
const packageJson = JSON.parse(
|
|
284
312
|
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
|
|
@@ -312,14 +340,6 @@ function printUsage() {
|
|
|
312
340
|
printLine(renderRootHelp())
|
|
313
341
|
}
|
|
314
342
|
|
|
315
|
-
function printPreviewUsage() {
|
|
316
|
-
printLine(`\nLocal app preview:\n\n orizu apps preview --file <path> --input-schema <json-path> --output-schema <json-path> --sample-row <json-path> [--screenshot <png-path>] [--headed] [--keep-open] [--component <name>]`)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function printOptimizationUsage() {
|
|
320
|
-
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] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n>] [--num-threads auto|N] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--log-dir logs] [--no-skip-perfect-parent-reflection]\n orizu optimizations export <run-id> [--out <path>] [--json]\n orizu optimizations pause <run-id> [--reason <text>] [--json]\n orizu optimizations resume <run-id> [--json]\n orizu optimizations finish <run-id> [--best-score <n>] [--best-candidate <id>] [--result-prompt-version <id>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations fail <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations cancel <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--json]`)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
343
|
let cliArgs = process.argv.slice(2)
|
|
324
344
|
let cliJsonOutput = false
|
|
325
345
|
|
|
@@ -351,6 +371,20 @@ function getArgs(name: string): string[] {
|
|
|
351
371
|
return values
|
|
352
372
|
}
|
|
353
373
|
|
|
374
|
+
function getOptionalArgValue(name: string): string | null {
|
|
375
|
+
const index = cliArgs.indexOf(name)
|
|
376
|
+
if (index === -1 || index + 1 >= cliArgs.length) {
|
|
377
|
+
return null
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const value = cliArgs[index + 1]
|
|
381
|
+
if (!value || value.startsWith('-')) {
|
|
382
|
+
return null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return value
|
|
386
|
+
}
|
|
387
|
+
|
|
354
388
|
function rejectDashPrefixedOptionValue(name: string, value: string | null) {
|
|
355
389
|
if (value && value.startsWith('-')) {
|
|
356
390
|
throw new Error(`Invalid value for ${name}: option values cannot start with a dash`)
|
|
@@ -409,6 +443,10 @@ export function sanitizeTerminalText(value: unknown): string {
|
|
|
409
443
|
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
|
|
410
444
|
}
|
|
411
445
|
|
|
446
|
+
function printError(message: string): void {
|
|
447
|
+
console.error(sanitizeTerminalText(message))
|
|
448
|
+
}
|
|
449
|
+
|
|
412
450
|
export function validateBrowserUrl(url: string, expectedOrigin?: string): URL {
|
|
413
451
|
let parsed: URL
|
|
414
452
|
try {
|
|
@@ -576,8 +614,8 @@ function readJsonObjectArg(valueArg: string | null, label: string): Record<strin
|
|
|
576
614
|
throw new Error('JSON root must be an object')
|
|
577
615
|
}
|
|
578
616
|
return parsed as Record<string, unknown>
|
|
579
|
-
} catch (error:
|
|
580
|
-
throw new Error(`Invalid ${label} '${valueArg}': ${
|
|
617
|
+
} catch (error: unknown) {
|
|
618
|
+
throw new Error(`Invalid ${label} '${valueArg}': ${getErrorMessage(error)}`)
|
|
581
619
|
}
|
|
582
620
|
}
|
|
583
621
|
|
|
@@ -689,6 +727,55 @@ async function fetchTeamMembers(teamSlug: string): Promise<TeamMember[]> {
|
|
|
689
727
|
return data.members
|
|
690
728
|
}
|
|
691
729
|
|
|
730
|
+
async function createTeamOnServer(name: string): Promise<Team> {
|
|
731
|
+
const response = await authedFetch('/api/cli/teams', {
|
|
732
|
+
method: 'POST',
|
|
733
|
+
headers: { 'Content-Type': 'application/json' },
|
|
734
|
+
body: JSON.stringify({ name }),
|
|
735
|
+
})
|
|
736
|
+
|
|
737
|
+
if (!response.ok) {
|
|
738
|
+
throw new Error(`Failed to create team: ${await response.text()}`)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const data = await parseJsonResponse<{ team: Team }>(response, 'Team create')
|
|
742
|
+
return data.team
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async function createProjectOnServer(teamSlug: string, name: string): Promise<Project> {
|
|
746
|
+
const response = await authedFetch('/api/cli/projects', {
|
|
747
|
+
method: 'POST',
|
|
748
|
+
headers: { 'Content-Type': 'application/json' },
|
|
749
|
+
body: JSON.stringify({ teamSlug, name }),
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
throw new Error(`Failed to create project: ${await response.text()}`)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const data = await parseJsonResponse<{
|
|
757
|
+
project: {
|
|
758
|
+
id: string
|
|
759
|
+
name: string
|
|
760
|
+
slug: string
|
|
761
|
+
teamId?: string
|
|
762
|
+
teamName?: string
|
|
763
|
+
teamSlug: string
|
|
764
|
+
role?: string
|
|
765
|
+
}
|
|
766
|
+
}>(response, 'Project create')
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
id: data.project.id,
|
|
770
|
+
name: data.project.name,
|
|
771
|
+
slug: data.project.slug,
|
|
772
|
+
teamId: data.project.teamId || '',
|
|
773
|
+
teamName: data.project.teamName || data.project.teamSlug,
|
|
774
|
+
teamSlug: data.project.teamSlug,
|
|
775
|
+
role: data.project.role || 'admin',
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
692
779
|
async function resolveProjectSelection(projectArg: string | null): Promise<Project> {
|
|
693
780
|
projectArg = projectArg || process.env.ORIZU_PROJECT || null
|
|
694
781
|
const teams = await fetchTeams()
|
|
@@ -724,7 +811,7 @@ async function resolveProjectSelection(projectArg: string | null): Promise<Proje
|
|
|
724
811
|
|
|
725
812
|
const matchedTeam = teams.find(team => team.slug === teamSlug)
|
|
726
813
|
if (!matchedTeam) {
|
|
727
|
-
|
|
814
|
+
printError(`Team '${teamSlug}' not found in your accessible teams.`)
|
|
728
815
|
const selectedTeam = await promptSelect(
|
|
729
816
|
'Select a team',
|
|
730
817
|
teams,
|
|
@@ -745,7 +832,7 @@ async function resolveProjectSelection(projectArg: string | null): Promise<Proje
|
|
|
745
832
|
const matchedProject = projects.find(project => project.slug === projectSlug)
|
|
746
833
|
|
|
747
834
|
if (!matchedProject) {
|
|
748
|
-
|
|
835
|
+
printError(`Project '${projectSlug}' not found in team '${matchedTeam.slug}'.`)
|
|
749
836
|
const selectedProject = await promptSelect(
|
|
750
837
|
`Select a project in ${matchedTeam.slug}`,
|
|
751
838
|
projects,
|
|
@@ -2771,22 +2858,12 @@ async function createTeam() {
|
|
|
2771
2858
|
throw new Error('Usage: orizu teams create --name <name>')
|
|
2772
2859
|
}
|
|
2773
2860
|
|
|
2774
|
-
const
|
|
2775
|
-
method: 'POST',
|
|
2776
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2777
|
-
body: JSON.stringify({ name }),
|
|
2778
|
-
})
|
|
2779
|
-
|
|
2780
|
-
if (!response.ok) {
|
|
2781
|
-
throw new Error(`Failed to create team: ${await response.text()}`)
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
|
-
const data = await parseJsonResponse<{ team: Team }>(response, 'Team create')
|
|
2861
|
+
const team = await createTeamOnServer(name)
|
|
2785
2862
|
if (hasJsonFlag()) {
|
|
2786
|
-
printJson({ team
|
|
2863
|
+
printJson({ team })
|
|
2787
2864
|
return
|
|
2788
2865
|
}
|
|
2789
|
-
printLine(`Created team: ${sanitizeTerminalText(
|
|
2866
|
+
printLine(`Created team: ${sanitizeTerminalText(team.name)} (${sanitizeTerminalText(team.slug)})`)
|
|
2790
2867
|
}
|
|
2791
2868
|
|
|
2792
2869
|
async function listProjects() {
|
|
@@ -2819,24 +2896,12 @@ async function createProject() {
|
|
|
2819
2896
|
teamSlug = team.slug
|
|
2820
2897
|
}
|
|
2821
2898
|
|
|
2822
|
-
const
|
|
2823
|
-
method: 'POST',
|
|
2824
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2825
|
-
body: JSON.stringify({ teamSlug, name }),
|
|
2826
|
-
})
|
|
2827
|
-
|
|
2828
|
-
if (!response.ok) {
|
|
2829
|
-
throw new Error(`Failed to create project: ${await response.text()}`)
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
const data = await parseJsonResponse<{
|
|
2833
|
-
project: { id: string; name: string; slug: string; teamSlug: string }
|
|
2834
|
-
}>(response, 'Project create')
|
|
2899
|
+
const project = await createProjectOnServer(teamSlug, name)
|
|
2835
2900
|
if (hasJsonFlag()) {
|
|
2836
|
-
printJson({ project
|
|
2901
|
+
printJson({ project })
|
|
2837
2902
|
return
|
|
2838
2903
|
}
|
|
2839
|
-
printLine(`Created project ${sanitizeTerminalText(`${
|
|
2904
|
+
printLine(`Created project ${sanitizeTerminalText(`${project.teamSlug}/${project.slug}`)}`)
|
|
2840
2905
|
}
|
|
2841
2906
|
|
|
2842
2907
|
async function listTasks() {
|
|
@@ -2863,11 +2928,11 @@ function readSourceFile(pathArg: string): string {
|
|
|
2863
2928
|
const expandedPath = expandHomePath(pathArg)
|
|
2864
2929
|
try {
|
|
2865
2930
|
return readFileSync(expandedPath, 'utf-8')
|
|
2866
|
-
} catch (error:
|
|
2867
|
-
if (error
|
|
2931
|
+
} catch (error: unknown) {
|
|
2932
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2868
2933
|
throw new Error(`File not found: ${expandedPath}`)
|
|
2869
2934
|
}
|
|
2870
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2935
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`)
|
|
2871
2936
|
}
|
|
2872
2937
|
}
|
|
2873
2938
|
|
|
@@ -2875,11 +2940,11 @@ function readSourceBytes(pathArg: string): Buffer {
|
|
|
2875
2940
|
const expandedPath = expandHomePath(pathArg)
|
|
2876
2941
|
try {
|
|
2877
2942
|
return readFileSync(expandedPath)
|
|
2878
|
-
} catch (error:
|
|
2879
|
-
if (error
|
|
2943
|
+
} catch (error: unknown) {
|
|
2944
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2880
2945
|
throw new Error(`File not found: ${expandedPath}`)
|
|
2881
2946
|
}
|
|
2882
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2947
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`)
|
|
2883
2948
|
}
|
|
2884
2949
|
}
|
|
2885
2950
|
|
|
@@ -2921,6 +2986,304 @@ async function askYesNo(question: string, defaultYes: boolean): Promise<boolean>
|
|
|
2921
2986
|
}
|
|
2922
2987
|
}
|
|
2923
2988
|
|
|
2989
|
+
async function askText(question: string, defaultValue: string): Promise<string> {
|
|
2990
|
+
const rl = createInterface({ input, output })
|
|
2991
|
+
try {
|
|
2992
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : ''
|
|
2993
|
+
const answer = (await rl.question(`${question}${suffix} `)).trim()
|
|
2994
|
+
return answer || defaultValue
|
|
2995
|
+
} finally {
|
|
2996
|
+
rl.close()
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
type KeypressInfo = {
|
|
3001
|
+
name?: string
|
|
3002
|
+
sequence?: string
|
|
3003
|
+
ctrl?: boolean
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
type RawInput = typeof input & {
|
|
3007
|
+
setRawMode?: (mode: boolean) => void
|
|
3008
|
+
isRaw?: boolean
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
async function waitForAnyKeyOrEscape(message: string): Promise<boolean> {
|
|
3012
|
+
if (!isInteractiveTerminal()) {
|
|
3013
|
+
throw new Error('Interactive terminal required.')
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
printLine(message)
|
|
3017
|
+
const rawInput = input as RawInput
|
|
3018
|
+
const wasRaw = Boolean(rawInput.isRaw)
|
|
3019
|
+
emitKeypressEvents(input)
|
|
3020
|
+
|
|
3021
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3022
|
+
function cleanup() {
|
|
3023
|
+
input.off('keypress', onKeypress)
|
|
3024
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
3025
|
+
rawInput.setRawMode(false)
|
|
3026
|
+
}
|
|
3027
|
+
input.pause()
|
|
3028
|
+
printLine('')
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
function onKeypress(_chunk: string, key: KeypressInfo) {
|
|
3032
|
+
cleanup()
|
|
3033
|
+
if (key.ctrl && key.name === 'c') {
|
|
3034
|
+
rejectPromise(new Error('Setup cancelled.'))
|
|
3035
|
+
return
|
|
3036
|
+
}
|
|
3037
|
+
resolvePromise(key.name !== 'escape')
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
input.on('keypress', onKeypress)
|
|
3041
|
+
input.resume()
|
|
3042
|
+
rawInput.setRawMode?.(true)
|
|
3043
|
+
})
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
async function promptKeyboardSelect<T>(
|
|
3047
|
+
title: string,
|
|
3048
|
+
items: T[],
|
|
3049
|
+
label: (item: T, index: number) => string
|
|
3050
|
+
): Promise<T> {
|
|
3051
|
+
if (items.length === 0) {
|
|
3052
|
+
throw new Error(`No options available for ${title.toLowerCase()}`)
|
|
3053
|
+
}
|
|
3054
|
+
if (!isInteractiveTerminal()) {
|
|
3055
|
+
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`)
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
printLine(`\n${sanitizeTerminalText(title)}`)
|
|
3059
|
+
printLine('Use ↑/↓ to choose, Enter to confirm, or Esc to cancel.')
|
|
3060
|
+
|
|
3061
|
+
let selected = 0
|
|
3062
|
+
let rendered = false
|
|
3063
|
+
const rawInput = input as RawInput
|
|
3064
|
+
const wasRaw = Boolean(rawInput.isRaw)
|
|
3065
|
+
emitKeypressEvents(input)
|
|
3066
|
+
|
|
3067
|
+
function render() {
|
|
3068
|
+
if (rendered) {
|
|
3069
|
+
output.write(`\x1b[${items.length}A`)
|
|
3070
|
+
}
|
|
3071
|
+
items.forEach((item, index) => {
|
|
3072
|
+
output.write('\x1b[2K')
|
|
3073
|
+
const marker = index === selected ? '●' : '○'
|
|
3074
|
+
output.write(` ${marker} ${sanitizeTerminalText(label(item, index))}\n`)
|
|
3075
|
+
})
|
|
3076
|
+
rendered = true
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
render()
|
|
3080
|
+
output.write('\x1b[?25l')
|
|
3081
|
+
|
|
3082
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3083
|
+
function cleanup() {
|
|
3084
|
+
input.off('keypress', onKeypress)
|
|
3085
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
3086
|
+
rawInput.setRawMode(false)
|
|
3087
|
+
}
|
|
3088
|
+
input.pause()
|
|
3089
|
+
output.write('\x1b[?25h')
|
|
3090
|
+
printLine('')
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
function onKeypress(_chunk: string, key: KeypressInfo) {
|
|
3094
|
+
if (key.ctrl && key.name === 'c') {
|
|
3095
|
+
cleanup()
|
|
3096
|
+
rejectPromise(new Error('Setup cancelled.'))
|
|
3097
|
+
return
|
|
3098
|
+
}
|
|
3099
|
+
if (key.name === 'escape') {
|
|
3100
|
+
cleanup()
|
|
3101
|
+
rejectPromise(new Error('Setup cancelled.'))
|
|
3102
|
+
return
|
|
3103
|
+
}
|
|
3104
|
+
if (key.name === 'up') {
|
|
3105
|
+
selected = (selected - 1 + items.length) % items.length
|
|
3106
|
+
render()
|
|
3107
|
+
return
|
|
3108
|
+
}
|
|
3109
|
+
if (key.name === 'down') {
|
|
3110
|
+
selected = (selected + 1) % items.length
|
|
3111
|
+
render()
|
|
3112
|
+
return
|
|
3113
|
+
}
|
|
3114
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
3115
|
+
const item = items[selected]
|
|
3116
|
+
cleanup()
|
|
3117
|
+
resolvePromise(item)
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
input.on('keypress', onKeypress)
|
|
3122
|
+
input.resume()
|
|
3123
|
+
rawInput.setRawMode?.(true)
|
|
3124
|
+
})
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
async function promptKeyboardMultiSelect<T>(
|
|
3128
|
+
title: string,
|
|
3129
|
+
items: T[],
|
|
3130
|
+
label: (item: T, index: number) => string,
|
|
3131
|
+
defaultSelected: (item: T, index: number) => boolean
|
|
3132
|
+
): Promise<T[]> {
|
|
3133
|
+
if (items.length === 0) {
|
|
3134
|
+
return []
|
|
3135
|
+
}
|
|
3136
|
+
if (!isInteractiveTerminal()) {
|
|
3137
|
+
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`)
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
printLine(`\n${sanitizeTerminalText(title)}`)
|
|
3141
|
+
printLine('Use ↑/↓ to move, Space to toggle, Enter to confirm, or Esc to skip.')
|
|
3142
|
+
|
|
3143
|
+
let selected = 0
|
|
3144
|
+
let rendered = false
|
|
3145
|
+
const checked = new Set<number>()
|
|
3146
|
+
items.forEach((item, index) => {
|
|
3147
|
+
if (defaultSelected(item, index)) {
|
|
3148
|
+
checked.add(index)
|
|
3149
|
+
}
|
|
3150
|
+
})
|
|
3151
|
+
const rawInput = input as RawInput
|
|
3152
|
+
const wasRaw = Boolean(rawInput.isRaw)
|
|
3153
|
+
emitKeypressEvents(input)
|
|
3154
|
+
|
|
3155
|
+
function render() {
|
|
3156
|
+
if (rendered) {
|
|
3157
|
+
output.write(`\x1b[${items.length}A`)
|
|
3158
|
+
}
|
|
3159
|
+
items.forEach((item, index) => {
|
|
3160
|
+
output.write('\x1b[2K')
|
|
3161
|
+
const cursor = index === selected ? '›' : ' '
|
|
3162
|
+
const marker = checked.has(index) ? '◉' : '○'
|
|
3163
|
+
output.write(`${cursor} ${marker} ${sanitizeTerminalText(label(item, index))}\n`)
|
|
3164
|
+
})
|
|
3165
|
+
rendered = true
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
render()
|
|
3169
|
+
output.write('\x1b[?25l')
|
|
3170
|
+
|
|
3171
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3172
|
+
function cleanup() {
|
|
3173
|
+
input.off('keypress', onKeypress)
|
|
3174
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
3175
|
+
rawInput.setRawMode(false)
|
|
3176
|
+
}
|
|
3177
|
+
input.pause()
|
|
3178
|
+
output.write('\x1b[?25h')
|
|
3179
|
+
printLine('')
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
function onKeypress(_chunk: string, key: KeypressInfo) {
|
|
3183
|
+
if (key.ctrl && key.name === 'c') {
|
|
3184
|
+
cleanup()
|
|
3185
|
+
rejectPromise(new Error('Setup cancelled.'))
|
|
3186
|
+
return
|
|
3187
|
+
}
|
|
3188
|
+
if (key.name === 'escape') {
|
|
3189
|
+
cleanup()
|
|
3190
|
+
resolvePromise([])
|
|
3191
|
+
return
|
|
3192
|
+
}
|
|
3193
|
+
if (key.name === 'up') {
|
|
3194
|
+
selected = (selected - 1 + items.length) % items.length
|
|
3195
|
+
render()
|
|
3196
|
+
return
|
|
3197
|
+
}
|
|
3198
|
+
if (key.name === 'down') {
|
|
3199
|
+
selected = (selected + 1) % items.length
|
|
3200
|
+
render()
|
|
3201
|
+
return
|
|
3202
|
+
}
|
|
3203
|
+
if (key.name === 'space') {
|
|
3204
|
+
if (checked.has(selected)) {
|
|
3205
|
+
checked.delete(selected)
|
|
3206
|
+
} else {
|
|
3207
|
+
checked.add(selected)
|
|
3208
|
+
}
|
|
3209
|
+
render()
|
|
3210
|
+
return
|
|
3211
|
+
}
|
|
3212
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
3213
|
+
const selectedItems = items.filter((_item, index) => checked.has(index))
|
|
3214
|
+
cleanup()
|
|
3215
|
+
resolvePromise(selectedItems)
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
input.on('keypress', onKeypress)
|
|
3220
|
+
input.resume()
|
|
3221
|
+
rawInput.setRawMode?.(true)
|
|
3222
|
+
})
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
function formatFindingCount(count: number, label: string): string {
|
|
3226
|
+
return `${count} ${label}${count === 1 ? '' : 's'}`
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function validationSummary(findings: { severity: string }[], logPath: string | null): string {
|
|
3230
|
+
if (findings.length === 0) {
|
|
3231
|
+
return 'passed'
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
const errors = findings.filter(finding => finding.severity === 'error').length
|
|
3235
|
+
const warnings = findings.filter(finding => finding.severity === 'warning').length
|
|
3236
|
+
const infos = findings.filter(finding => finding.severity === 'info').length
|
|
3237
|
+
const parts = [
|
|
3238
|
+
errors > 0 ? formatFindingCount(errors, 'error') : null,
|
|
3239
|
+
warnings > 0 ? formatFindingCount(warnings, 'warning') : null,
|
|
3240
|
+
infos > 0 ? formatFindingCount(infos, 'info') : null,
|
|
3241
|
+
].filter(Boolean)
|
|
3242
|
+
const location = logPath ? ` (printed to ${logPath})` : ''
|
|
3243
|
+
return `${parts.join(', ')}${location}`
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
function writeWorkspaceValidationLog(
|
|
3247
|
+
workspaceRoot: string,
|
|
3248
|
+
findings: { severity: string, code: string, path?: string, message: string }[],
|
|
3249
|
+
dryRun: boolean
|
|
3250
|
+
): string | null {
|
|
3251
|
+
if (findings.length === 0 || dryRun) {
|
|
3252
|
+
return null
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
const logsDir = join(workspaceRoot, '.logs')
|
|
3256
|
+
mkdirSync(logsDir, { recursive: true })
|
|
3257
|
+
const logPath = join(logsDir, `${randomBytes(6).toString('hex')}.log`)
|
|
3258
|
+
const lines = [
|
|
3259
|
+
'Orizu workspace validation',
|
|
3260
|
+
`Generated at: ${new Date().toISOString()}`,
|
|
3261
|
+
`Workspace: ${workspaceRoot}`,
|
|
3262
|
+
`Summary: ${validationSummary(findings, null)}`,
|
|
3263
|
+
'',
|
|
3264
|
+
...findings.map(finding => {
|
|
3265
|
+
const path = finding.path ? ` ${finding.path}` : ''
|
|
3266
|
+
return `${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`
|
|
3267
|
+
}),
|
|
3268
|
+
'',
|
|
3269
|
+
]
|
|
3270
|
+
writeFileSync(logPath, lines.join('\n'), 'utf8')
|
|
3271
|
+
return relative(workspaceRoot, logPath)
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function printWorkspaceFindings(findings: { severity: string, code: string, path?: string, message: string }[]) {
|
|
3275
|
+
if (findings.length === 0) {
|
|
3276
|
+
printLine(' Validation passed.')
|
|
3277
|
+
return
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
printLine(' Validation findings:')
|
|
3281
|
+
for (const finding of findings) {
|
|
3282
|
+
const path = finding.path ? ` ${finding.path}` : ''
|
|
3283
|
+
printLine(` - ${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`)
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
|
|
2924
3287
|
const SKILL_INSTALL_USAGE =
|
|
2925
3288
|
`Usage: orizu install-skill [--agent <${SKILL_INSTALL_AGENTS.join('|')}>]... [--scope global|project] [--mode auto|link|copy] [--target <${SKILL_INSTALL_TARGETS.join('|')}>]... [--yes] [--dry-run]`
|
|
2926
3289
|
|
|
@@ -2933,6 +3296,7 @@ interface SkillInstallChoice {
|
|
|
2933
3296
|
function describeSkillTarget(target: SkillInstallTarget): string {
|
|
2934
3297
|
if (target === 'claude-user') return 'Claude Code, for you across all projects'
|
|
2935
3298
|
if (target === 'claude-project') return 'Claude Code, only for this project'
|
|
3299
|
+
if (target === 'codex-user') return 'Codex, for you across all projects'
|
|
2936
3300
|
if (target === 'agent-user') return 'Codex / Open Agent Skills, for you across all projects'
|
|
2937
3301
|
if (target === 'agents-project') return 'Codex / Open Agent Skills, only for this project'
|
|
2938
3302
|
if (target === 'codex-project') return 'Codex legacy project folder (.codex/skills)'
|
|
@@ -3053,15 +3417,31 @@ interface SkillInstallOutcome {
|
|
|
3053
3417
|
|
|
3054
3418
|
async function applySkillInstallTargets(
|
|
3055
3419
|
targets: SkillInstallTarget[],
|
|
3056
|
-
options: {
|
|
3420
|
+
options: {
|
|
3421
|
+
mode: SkillInstallMode
|
|
3422
|
+
skipConfirm: boolean
|
|
3423
|
+
dryRun: boolean
|
|
3424
|
+
cwd?: string
|
|
3425
|
+
homeDir?: string
|
|
3426
|
+
showPlan?: boolean
|
|
3427
|
+
printResults?: boolean
|
|
3428
|
+
}
|
|
3057
3429
|
): Promise<SkillInstallOutcome[]> {
|
|
3058
3430
|
const cliVersion = getCliVersion()
|
|
3059
3431
|
const jsonMode = hasJsonFlag()
|
|
3060
3432
|
const planned = targets.map(target =>
|
|
3061
|
-
installSkillTarget(target, {
|
|
3433
|
+
installSkillTarget(target, {
|
|
3434
|
+
cwd: options.cwd,
|
|
3435
|
+
homeDir: options.homeDir,
|
|
3436
|
+
dryRun: true,
|
|
3437
|
+
mode: options.mode,
|
|
3438
|
+
cliVersion,
|
|
3439
|
+
})
|
|
3062
3440
|
)
|
|
3441
|
+
const showPlan = options.showPlan ?? true
|
|
3442
|
+
const printResults = options.printResults ?? true
|
|
3063
3443
|
|
|
3064
|
-
if (!jsonMode) {
|
|
3444
|
+
if (showPlan && !jsonMode) {
|
|
3065
3445
|
printLine('Planned writes:')
|
|
3066
3446
|
for (const plan of planned) {
|
|
3067
3447
|
printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path))
|
|
@@ -3070,7 +3450,7 @@ async function applySkillInstallTargets(
|
|
|
3070
3450
|
}
|
|
3071
3451
|
|
|
3072
3452
|
if (options.dryRun) {
|
|
3073
|
-
if (!jsonMode) {
|
|
3453
|
+
if (printResults && !jsonMode) {
|
|
3074
3454
|
printLine('Dry run: no files were changed.')
|
|
3075
3455
|
}
|
|
3076
3456
|
return planned.map(plan => ({
|
|
@@ -3084,8 +3464,8 @@ async function applySkillInstallTargets(
|
|
|
3084
3464
|
const outcomes: SkillInstallOutcome[] = []
|
|
3085
3465
|
for (const target of targets) {
|
|
3086
3466
|
let overwrite = options.skipConfirm
|
|
3087
|
-
const installPath = getSkillInstallPath(target)
|
|
3088
|
-
if (!overwrite && targetNeedsOverwrite(target)) {
|
|
3467
|
+
const installPath = getSkillInstallPath(target, { cwd: options.cwd, homeDir: options.homeDir })
|
|
3468
|
+
if (!overwrite && targetNeedsOverwrite(target, { cwd: options.cwd, homeDir: options.homeDir })) {
|
|
3089
3469
|
if (!isInteractiveTerminal()) {
|
|
3090
3470
|
throw new Error(`${installPath} already exists. Pass --yes to replace it.`)
|
|
3091
3471
|
}
|
|
@@ -3101,11 +3481,13 @@ async function applySkillInstallTargets(
|
|
|
3101
3481
|
|
|
3102
3482
|
try {
|
|
3103
3483
|
const result = installSkillTarget(target, {
|
|
3484
|
+
cwd: options.cwd,
|
|
3485
|
+
homeDir: options.homeDir,
|
|
3104
3486
|
overwrite,
|
|
3105
3487
|
mode: options.mode,
|
|
3106
3488
|
cliVersion,
|
|
3107
3489
|
})
|
|
3108
|
-
if (!jsonMode) {
|
|
3490
|
+
if (printResults && !jsonMode) {
|
|
3109
3491
|
const suffix = result.mode === 'link' ? ' (symlink)' : ''
|
|
3110
3492
|
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`)
|
|
3111
3493
|
}
|
|
@@ -3115,9 +3497,9 @@ async function applySkillInstallTargets(
|
|
|
3115
3497
|
action: result.action as 'created' | 'updated',
|
|
3116
3498
|
mode: result.mode,
|
|
3117
3499
|
})
|
|
3118
|
-
} catch (error:
|
|
3119
|
-
const message =
|
|
3120
|
-
if (!jsonMode) {
|
|
3500
|
+
} catch (error: unknown) {
|
|
3501
|
+
const message = getErrorMessage(error)
|
|
3502
|
+
if (printResults && !jsonMode) {
|
|
3121
3503
|
printLine(`Failed ${installPath}: ${message}`)
|
|
3122
3504
|
}
|
|
3123
3505
|
outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message })
|
|
@@ -3295,17 +3677,161 @@ function describeSetupAuthState(): { state: 'signed-in' | 'signed-out', baseUrl:
|
|
|
3295
3677
|
}
|
|
3296
3678
|
}
|
|
3297
3679
|
|
|
3680
|
+
type SetupTeamChoice =
|
|
3681
|
+
| { kind: 'team', team: Team }
|
|
3682
|
+
| { kind: 'create' }
|
|
3683
|
+
|
|
3684
|
+
async function resolveSetupTeam(teamSlugArg: string | null, noInput: boolean, dryRun: boolean): Promise<Team> {
|
|
3685
|
+
const teams = await fetchTeams()
|
|
3686
|
+
const normalizedTeamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null
|
|
3687
|
+
|
|
3688
|
+
if (normalizedTeamSlug) {
|
|
3689
|
+
const team = teams.find(candidate => candidate.slug === normalizedTeamSlug)
|
|
3690
|
+
if (!team) {
|
|
3691
|
+
throw new Error(`Team '${normalizedTeamSlug}' was not found in your accessible teams.`)
|
|
3692
|
+
}
|
|
3693
|
+
return team
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
if (noInput || dryRun) {
|
|
3697
|
+
throw new Error('Authenticated setup requires --team <slug> when running non-interactively.')
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
const choices: SetupTeamChoice[] = [
|
|
3701
|
+
...teams.map(team => ({ kind: 'team' as const, team })),
|
|
3702
|
+
{ kind: 'create' },
|
|
3703
|
+
]
|
|
3704
|
+
const choice = await promptKeyboardSelect(
|
|
3705
|
+
'Choose the team to set up in this directory',
|
|
3706
|
+
choices,
|
|
3707
|
+
item => item.kind === 'create'
|
|
3708
|
+
? 'Create a new team'
|
|
3709
|
+
: `${item.team.name} (${item.team.slug})`
|
|
3710
|
+
)
|
|
3711
|
+
|
|
3712
|
+
if (choice.kind === 'team') {
|
|
3713
|
+
return choice.team
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
const name = await askText(' Team name?', '')
|
|
3717
|
+
if (!name) {
|
|
3718
|
+
throw new Error('Team name is required to create a team.')
|
|
3719
|
+
}
|
|
3720
|
+
return createTeamOnServer(name)
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
async function resolveSetupProjects(team: Team): Promise<Project[]> {
|
|
3724
|
+
const projects = await fetchProjects(team.slug)
|
|
3725
|
+
|
|
3726
|
+
if (projects.length === 0) {
|
|
3727
|
+
throw new Error(`Team '${team.slug}' has no projects. Create a project before running setup.`)
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
return projects
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
function projectSeedsFromProjects(projects: Project[]): WorkspaceProjectSeed[] {
|
|
3734
|
+
return projects.map(project => ({
|
|
3735
|
+
slug: project.slug,
|
|
3736
|
+
id: project.id,
|
|
3737
|
+
name: project.name,
|
|
3738
|
+
}))
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
function formatProjectSetupProgress(count: number): string {
|
|
3742
|
+
return `${count} project${count === 1 ? '' : 's'} being set up in workspace...`
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
interface SetupSkillChoice {
|
|
3746
|
+
agent: SkillInstallAgent
|
|
3747
|
+
label: string
|
|
3748
|
+
target: SkillInstallTarget
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
function setupTargetForAgent(agent: SkillInstallAgent): SkillInstallTarget {
|
|
3752
|
+
return agent === 'codex' ? 'codex-user' : 'claude-user'
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
function resolveSetupSkillHome(): string {
|
|
3756
|
+
return resolve(process.env.HOME || homedir())
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
function detectedSetupSkillChoices(homeDir: string): SetupSkillChoice[] {
|
|
3760
|
+
const choices: SetupSkillChoice[] = []
|
|
3761
|
+
if (existsSync(join(homeDir, '.codex'))) {
|
|
3762
|
+
choices.push({
|
|
3763
|
+
agent: 'codex',
|
|
3764
|
+
label: 'Codex',
|
|
3765
|
+
target: 'codex-user',
|
|
3766
|
+
})
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
if (existsSync(join(homeDir, '.claude'))) {
|
|
3770
|
+
choices.push({
|
|
3771
|
+
agent: 'claude',
|
|
3772
|
+
label: 'Claude Code',
|
|
3773
|
+
target: 'claude-user',
|
|
3774
|
+
})
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
return choices
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
function setupSkillTargetsFromArgs(): SkillInstallTarget[] {
|
|
3781
|
+
const targets: SkillInstallTarget[] = []
|
|
3782
|
+
for (const agent of getArgs('--agent')) {
|
|
3783
|
+
if (!isSkillInstallAgent(agent)) {
|
|
3784
|
+
throw new Error(
|
|
3785
|
+
`Unknown agent '${agent}'. Available agents: ${SKILL_INSTALL_AGENTS.join(', ')}`
|
|
3786
|
+
)
|
|
3787
|
+
}
|
|
3788
|
+
const target = setupTargetForAgent(agent)
|
|
3789
|
+
if (!targets.includes(target)) {
|
|
3790
|
+
targets.push(target)
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
for (const target of getArgs('--target')) {
|
|
3794
|
+
if (!isSkillInstallTarget(target)) {
|
|
3795
|
+
throw new Error(
|
|
3796
|
+
`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`
|
|
3797
|
+
)
|
|
3798
|
+
}
|
|
3799
|
+
if (!targets.includes(target)) {
|
|
3800
|
+
targets.push(target)
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return targets
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
function setupSkillLabel(target: SkillInstallTarget): string {
|
|
3807
|
+
if (target === 'codex-user' || target === 'codex-project' || target === 'agent-user' || target === 'agents-project') {
|
|
3808
|
+
return 'Codex skill'
|
|
3809
|
+
}
|
|
3810
|
+
if (target === 'claude-user' || target === 'claude-project') return 'Claude Code skill'
|
|
3811
|
+
return 'Orizu skill'
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3298
3814
|
async function setupCommand() {
|
|
3299
3815
|
const dryRun = hasArg('--dry-run')
|
|
3816
|
+
const validateOnly = hasArg('--validate') && !hasArg('--fix')
|
|
3817
|
+
const fix = hasArg('--fix')
|
|
3818
|
+
const verbose = hasArg('--verbose')
|
|
3819
|
+
const handoffRequested = hasArg('--handoff') || Boolean(getArg('--launch'))
|
|
3300
3820
|
const skipConfirm = hasArg('--yes')
|
|
3301
|
-
const noInput = hasArg('--no-input') || !isInteractiveTerminal()
|
|
3821
|
+
const noInput = hasArg('--no-input') || hasArg('--non-interactive') || !isInteractiveTerminal()
|
|
3822
|
+
const workspaceArg = getOptionalArgValue('--workspace')
|
|
3823
|
+
const workspaceRoot = workspaceArg ? resolve(expandHomePath(workspaceArg)) : process.cwd()
|
|
3824
|
+
const teamArg = getOptionalArgValue('--team')
|
|
3825
|
+
const projectArg = getOptionalArgValue('--project')
|
|
3826
|
+
const setupSkillHome = resolveSetupSkillHome()
|
|
3827
|
+
const installMode: SkillInstallMode = getArg('--mode') ? parseSkillInstallMode() : 'link'
|
|
3302
3828
|
|
|
3303
3829
|
printBannerIfInteractive()
|
|
3304
3830
|
printLine('Orizu setup')
|
|
3305
3831
|
printLine('')
|
|
3306
3832
|
|
|
3307
|
-
//
|
|
3308
|
-
printLine('1
|
|
3833
|
+
// Step 1: login
|
|
3834
|
+
printLine('Step 1: Login')
|
|
3309
3835
|
let auth = describeSetupAuthState()
|
|
3310
3836
|
if (hasArg('--skip-login')) {
|
|
3311
3837
|
printLine(' Skipped (--skip-login).')
|
|
@@ -3313,70 +3839,159 @@ async function setupCommand() {
|
|
|
3313
3839
|
printLine(` Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`)
|
|
3314
3840
|
} else if (noInput || dryRun) {
|
|
3315
3841
|
printLine(' Not signed in. Run `orizu login` to authenticate.')
|
|
3316
|
-
} else if (await
|
|
3842
|
+
} else if (await waitForAnyKeyOrEscape(' Press any key to open a browser and connect to your account, or Esc to cancel.')) {
|
|
3317
3843
|
await login()
|
|
3318
3844
|
auth = describeSetupAuthState()
|
|
3319
3845
|
} else {
|
|
3320
|
-
|
|
3321
|
-
}
|
|
3322
|
-
printLine('')
|
|
3323
|
-
|
|
3324
|
-
// Phase 2: agent integration
|
|
3325
|
-
printLine('2. Agent integration')
|
|
3326
|
-
const scope = parseSkillInstallScope()
|
|
3327
|
-
const mode = parseSkillInstallMode()
|
|
3328
|
-
let targets = hasArg('--no-install') ? [] : parseSkillInstallTargets(scope)
|
|
3329
|
-
if (targets.length === 0 && !hasArg('--no-install')) {
|
|
3330
|
-
if (noInput) {
|
|
3331
|
-
printLine(' No agents selected. Pass --agent claude and/or --agent codex, or rerun interactively.')
|
|
3332
|
-
} else {
|
|
3333
|
-
targets = await promptSkillInstallTargets(scope)
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
|
|
3337
|
-
let installOutcomes: SkillInstallOutcome[] = []
|
|
3338
|
-
if (targets.length > 0) {
|
|
3339
|
-
installOutcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun })
|
|
3340
|
-
} else if (hasArg('--no-install')) {
|
|
3341
|
-
printLine(' Skipped (--no-install).')
|
|
3846
|
+
throw new Error('Setup cancelled.')
|
|
3342
3847
|
}
|
|
3343
3848
|
printLine('')
|
|
3344
3849
|
|
|
3345
|
-
//
|
|
3346
|
-
printLine('
|
|
3347
|
-
let workspaceState: 'created' | 'exists' | 'skipped' | 'would-create' = 'skipped'
|
|
3348
|
-
|
|
3850
|
+
// Step 2: local workspace
|
|
3851
|
+
printLine('Step 2: Setup your workspace')
|
|
3852
|
+
let workspaceState: 'created' | 'exists' | 'skipped' | 'would-create' | 'validated' | 'invalid' | 'repaired' = 'skipped'
|
|
3853
|
+
let workspaceResult: ReturnType<typeof initOrizuWorkspace> | null = null
|
|
3854
|
+
let validationLogPath: string | null = null
|
|
3855
|
+
let hostedAttached = false
|
|
3856
|
+
if (hasArg('--no-workspace') && !validateOnly && !fix) {
|
|
3349
3857
|
printLine(' Skipped (--no-workspace).')
|
|
3350
|
-
} else if (workspaceExists()) {
|
|
3351
|
-
workspaceState = 'exists'
|
|
3352
|
-
printLine(` ${WORKSPACE_DIR_NAME}/ already initialized.`)
|
|
3353
3858
|
} else {
|
|
3354
|
-
const wantsWorkspace =
|
|
3355
|
-
|
|
3859
|
+
const wantsWorkspace = validateOnly
|
|
3860
|
+
|| fix
|
|
3861
|
+
|| hasArg('--workspace')
|
|
3862
|
+
|| Boolean(teamArg || projectArg)
|
|
3863
|
+
|| workspaceExists(workspaceRoot)
|
|
3864
|
+
|| !noInput
|
|
3356
3865
|
if (wantsWorkspace) {
|
|
3866
|
+
let teamSlug = teamArg
|
|
3867
|
+
const projectSlug = projectArg
|
|
3868
|
+
let teamId: string | null = null
|
|
3869
|
+
let projects: WorkspaceProjectSeed[] | undefined
|
|
3870
|
+
|
|
3871
|
+
if (auth.state === 'signed-in' && !validateOnly) {
|
|
3872
|
+
const team = await resolveSetupTeam(teamSlug, noInput, dryRun)
|
|
3873
|
+
const serverProjects = await resolveSetupProjects(team)
|
|
3874
|
+
teamSlug = team.slug
|
|
3875
|
+
teamId = team.id
|
|
3876
|
+
projects = projectSeedsFromProjects(serverProjects)
|
|
3877
|
+
printLine(` ${formatProjectSetupProgress(projects.length)}`)
|
|
3878
|
+
if (!hasArg('--local') && !noInput && !dryRun) { let link: GithubLinkResult | null = null; try { link = await runGithubLink(team.slug, { print: printLine, fetcher: authedFetch, openUrl: openInBrowser, confirm: (org) => askYesNo(` Connect this team to GitHub org '${org}'?`, true) }) } catch (error) { printLine(` GitHub link skipped: ${getErrorMessage(error)} Run \`orizu github link --team ${team.slug}\` to finish.`) }
|
|
3879
|
+
if (link) { try { hostedAttached = (await runInteractiveHostedSetup({ teamSlug: team.slug, targetDir: workspaceRoot, local: hasArg('--local'), activeInstallation: link.status === 'active' }, { print: printLine, fetcher: authedFetch })).attached } catch (error) { throw new Error(`Hosted setup failed: ${getErrorMessage(error)} Remove any partial clone in ${workspaceRoot} if one exists, then re-run \`orizu setup\` (or pass --local to scaffold a local-only workspace).`) } } }
|
|
3880
|
+
} else if (!teamSlug && !noInput && !validateOnly) {
|
|
3881
|
+
teamSlug = await askText(' Team slug?', 'local-team')
|
|
3882
|
+
}
|
|
3883
|
+
if (auth.state !== 'signed-in' && (!teamSlug || !projectSlug) && noInput && hasArg('--workspace')) {
|
|
3884
|
+
printLine(' Using starter local-only team/project. Pass --team with authentication to materialize server projects.')
|
|
3885
|
+
}
|
|
3886
|
+
if (projectSlug && auth.state !== 'signed-in') {
|
|
3887
|
+
projects = [{ slug: projectSlug }]
|
|
3888
|
+
}
|
|
3889
|
+
if (auth.state !== 'signed-in' && !validateOnly) {
|
|
3890
|
+
const localProjectCount = projects?.length || 1
|
|
3891
|
+
printLine(` ${formatProjectSetupProgress(localProjectCount)}`)
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
if (hostedAttached) { workspaceState = 'exists' } else {
|
|
3357
3895
|
const result = initOrizuWorkspace({
|
|
3896
|
+
workspaceRoot,
|
|
3897
|
+
teamSlug,
|
|
3898
|
+
teamId,
|
|
3899
|
+
projectSlug: auth.state === 'signed-in' ? null : projectSlug,
|
|
3900
|
+
projects,
|
|
3358
3901
|
baseUrl: auth.baseUrl,
|
|
3902
|
+
serviceOrigin: getOptionalArgValue('--service-origin') || auth.baseUrl,
|
|
3903
|
+
attachWorkspaceId: getOptionalArgValue('--attach-workspace'),
|
|
3359
3904
|
cliVersion: getCliVersion(),
|
|
3360
3905
|
dryRun,
|
|
3906
|
+
validateOnly,
|
|
3907
|
+
fix,
|
|
3908
|
+
noSymlinks: hasArg('--no-symlinks'),
|
|
3361
3909
|
})
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3910
|
+
workspaceResult = result
|
|
3911
|
+
workspaceState = result.state
|
|
3912
|
+
if (dryRun || verbose) {
|
|
3913
|
+
for (const action of result.actions) {
|
|
3914
|
+
printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`)
|
|
3915
|
+
}
|
|
3916
|
+
} else if (result.actions.length > 0) {
|
|
3917
|
+
printLine(validateOnly ? ' Workspace validation complete' : ' Workspace setup complete')
|
|
3918
|
+
} else if (!validateOnly) {
|
|
3919
|
+
printLine(' Workspace setup complete')
|
|
3365
3920
|
}
|
|
3921
|
+
validationLogPath = writeWorkspaceValidationLog(result.root, result.findings, dryRun)
|
|
3922
|
+
if (verbose) {
|
|
3923
|
+
printWorkspaceFindings(result.findings)
|
|
3924
|
+
}
|
|
3925
|
+
if ((validateOnly || fix) && result.findings.some(finding => finding.severity === 'error')) {
|
|
3926
|
+
process.exitCode = 1
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
} else {
|
|
3930
|
+
printLine(` Skipped. Rerun with --workspace to initialize the workspace contract.`)
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
printLine('')
|
|
3934
|
+
|
|
3935
|
+
// Step 3: global coding-agent skills
|
|
3936
|
+
let installOutcomes: SkillInstallOutcome[] = []
|
|
3937
|
+
printLine('Step 3: Install coding agent skills')
|
|
3938
|
+
if (hasArg('--no-install') || validateOnly || fix) {
|
|
3939
|
+
printLine(hasArg('--no-install') ? ' Skipped (--no-install).' : ' Skipped for validation/repair.')
|
|
3940
|
+
} else {
|
|
3941
|
+
let targets = setupSkillTargetsFromArgs()
|
|
3942
|
+
if (targets.length === 0 && !noInput && !dryRun) {
|
|
3943
|
+
const choices = detectedSetupSkillChoices(setupSkillHome)
|
|
3944
|
+
if (choices.length > 0) {
|
|
3945
|
+
const selectedChoices = await promptKeyboardMultiSelect(
|
|
3946
|
+
'Choose the coding agents that should learn how to use Orizu',
|
|
3947
|
+
choices,
|
|
3948
|
+
choice => choice.label,
|
|
3949
|
+
() => true
|
|
3950
|
+
)
|
|
3951
|
+
targets = selectedChoices.map(choice => choice.target)
|
|
3952
|
+
} else {
|
|
3953
|
+
printLine(' No Codex or Claude Code agent folders were detected.')
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
if (targets.length === 0) {
|
|
3958
|
+
printLine(' Skipped skill install.')
|
|
3366
3959
|
} else {
|
|
3367
|
-
printLine(
|
|
3960
|
+
printLine(' Installing skills...')
|
|
3961
|
+
installOutcomes = await applySkillInstallTargets(targets, {
|
|
3962
|
+
mode: installMode,
|
|
3963
|
+
skipConfirm,
|
|
3964
|
+
dryRun,
|
|
3965
|
+
cwd: workspaceRoot,
|
|
3966
|
+
homeDir: setupSkillHome,
|
|
3967
|
+
showPlan: false,
|
|
3968
|
+
printResults: false,
|
|
3969
|
+
})
|
|
3970
|
+
if (dryRun) {
|
|
3971
|
+
printLine(' Dry run: no skill files were changed.')
|
|
3972
|
+
}
|
|
3973
|
+
for (const outcome of installOutcomes) {
|
|
3974
|
+
const label = setupSkillLabel(outcome.target)
|
|
3975
|
+
if (outcome.action === 'failed') {
|
|
3976
|
+
printLine(` ${label} failed: ${outcome.error}`)
|
|
3977
|
+
} else if (outcome.action === 'skipped') {
|
|
3978
|
+
printLine(` ${label} skipped`)
|
|
3979
|
+
} else {
|
|
3980
|
+
printLine(` ${label} installed`)
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3368
3983
|
}
|
|
3369
3984
|
}
|
|
3370
3985
|
printLine('')
|
|
3371
3986
|
|
|
3372
3987
|
// Phase 4: coding-agent handoff
|
|
3373
3988
|
const handoffPrompt = renderAgentSetupPrompt({
|
|
3374
|
-
workspacePath:
|
|
3375
|
-
? getWorkspaceRoot()
|
|
3989
|
+
workspacePath: workspaceResult && workspaceState !== 'invalid'
|
|
3990
|
+
? getWorkspaceRoot(workspaceRoot)
|
|
3376
3991
|
: null,
|
|
3377
3992
|
})
|
|
3378
|
-
if (!hasArg('--no-handoff')) {
|
|
3379
|
-
printLine('4
|
|
3993
|
+
if (handoffRequested && !hasArg('--no-handoff')) {
|
|
3994
|
+
printLine('Step 4: Coding-agent handoff')
|
|
3380
3995
|
printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:')
|
|
3381
3996
|
printLine('')
|
|
3382
3997
|
printLine('--- prompt start ---')
|
|
@@ -3416,7 +4031,7 @@ async function setupCommand() {
|
|
|
3416
4031
|
const summaryTargets = installOutcomes.length > 0
|
|
3417
4032
|
? installOutcomes
|
|
3418
4033
|
.filter(outcome => outcome.action !== 'failed')
|
|
3419
|
-
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
|
|
4034
|
+
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target, { cwd: workspaceRoot, homeDir: setupSkillHome }) }))
|
|
3420
4035
|
: []
|
|
3421
4036
|
const source = resolveSkillSource()
|
|
3422
4037
|
|
|
@@ -3436,7 +4051,21 @@ async function setupCommand() {
|
|
|
3436
4051
|
cliVersion: getCliVersion(),
|
|
3437
4052
|
skillHash: computeSkillContentHash(source.root),
|
|
3438
4053
|
},
|
|
3439
|
-
workspace: {
|
|
4054
|
+
workspace: {
|
|
4055
|
+
state: workspaceState,
|
|
4056
|
+
path: getWorkspaceRoot(workspaceRoot),
|
|
4057
|
+
validationLogPath,
|
|
4058
|
+
operations: workspaceResult?.operations.map(op => ({
|
|
4059
|
+
action: op.action,
|
|
4060
|
+
path: op.path,
|
|
4061
|
+
safe: op.safe,
|
|
4062
|
+
reason: op.reason,
|
|
4063
|
+
entries: op.entries,
|
|
4064
|
+
sourcePath: op.sourcePath,
|
|
4065
|
+
target: op.target,
|
|
4066
|
+
})) || [],
|
|
4067
|
+
findings: workspaceResult?.findings || [],
|
|
4068
|
+
},
|
|
3440
4069
|
})
|
|
3441
4070
|
return
|
|
3442
4071
|
}
|
|
@@ -3444,21 +4073,23 @@ async function setupCommand() {
|
|
|
3444
4073
|
printLine('Setup summary')
|
|
3445
4074
|
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`)
|
|
3446
4075
|
if (summaryTargets.length > 0) {
|
|
3447
|
-
printLine('
|
|
4076
|
+
printLine(' Skills:')
|
|
3448
4077
|
for (const { outcome, status } of summaryTargets) {
|
|
3449
4078
|
const modeLabel = status.mode ? `, ${status.mode}` : ''
|
|
3450
4079
|
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`)
|
|
3451
4080
|
}
|
|
3452
4081
|
} else {
|
|
3453
|
-
printLine('
|
|
4082
|
+
printLine(' Skills: none installed this run')
|
|
3454
4083
|
}
|
|
3455
4084
|
const failed = installOutcomes.filter(outcome => outcome.action === 'failed')
|
|
3456
4085
|
for (const failure of failed) {
|
|
3457
4086
|
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`)
|
|
3458
4087
|
}
|
|
3459
|
-
printLine(`
|
|
3460
|
-
|
|
3461
|
-
|
|
4088
|
+
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : getWorkspaceRoot(workspaceRoot)}`)
|
|
4089
|
+
if (workspaceResult) {
|
|
4090
|
+
printLine(` Validation: ${validationSummary(workspaceResult.findings, validationLogPath)}`)
|
|
4091
|
+
}
|
|
4092
|
+
printLine(' Next: run `orizu setup prompt` if you want a coding-agent handoff prompt.')
|
|
3462
4093
|
}
|
|
3463
4094
|
|
|
3464
4095
|
function setupPromptCommand() {
|
|
@@ -3481,8 +4112,8 @@ function readJsonFile(pathArg: string): Record<string, unknown> {
|
|
|
3481
4112
|
}
|
|
3482
4113
|
|
|
3483
4114
|
return parsed as Record<string, unknown>
|
|
3484
|
-
} catch (error:
|
|
3485
|
-
throw new Error(`Invalid JSON file '${pathArg}': ${
|
|
4115
|
+
} catch (error: unknown) {
|
|
4116
|
+
throw new Error(`Invalid JSON file '${pathArg}': ${getErrorMessage(error)}`)
|
|
3486
4117
|
}
|
|
3487
4118
|
}
|
|
3488
4119
|
|
|
@@ -3559,141 +4190,6 @@ function writeTextFileEnsuringDir(pathArg: string, content: string) {
|
|
|
3559
4190
|
writeFileSync(pathArg, content)
|
|
3560
4191
|
}
|
|
3561
4192
|
|
|
3562
|
-
function shouldExcludeArtifactPath(relativePath: string): boolean {
|
|
3563
|
-
const parts = relativePath.split('/')
|
|
3564
|
-
return parts.some(part =>
|
|
3565
|
-
part === '.git' ||
|
|
3566
|
-
part === '.DS_Store' ||
|
|
3567
|
-
part === '__pycache__' ||
|
|
3568
|
-
part === '.pytest_cache'
|
|
3569
|
-
)
|
|
3570
|
-
}
|
|
3571
|
-
|
|
3572
|
-
function collectArtifactFiles(sourceDir: string, relativeDir = ''): string[] {
|
|
3573
|
-
const absoluteDir = relativeDir ? join(sourceDir, relativeDir) : sourceDir
|
|
3574
|
-
return readdirSync(absoluteDir, { withFileTypes: true })
|
|
3575
|
-
.flatMap(entry => {
|
|
3576
|
-
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name
|
|
3577
|
-
if (shouldExcludeArtifactPath(relativePath)) {
|
|
3578
|
-
return []
|
|
3579
|
-
}
|
|
3580
|
-
if (entry.isDirectory()) {
|
|
3581
|
-
return collectArtifactFiles(sourceDir, relativePath)
|
|
3582
|
-
}
|
|
3583
|
-
return entry.isFile() ? [relativePath] : []
|
|
3584
|
-
})
|
|
3585
|
-
.sort((left, right) => left < right ? -1 : left > right ? 1 : 0)
|
|
3586
|
-
}
|
|
3587
|
-
|
|
3588
|
-
const ZIP_GENERAL_PURPOSE_UTF8 = 0x0800
|
|
3589
|
-
const ZIP_DOS_TIME_2000_01_01 = 0
|
|
3590
|
-
const ZIP_DOS_DATE_2000_01_01 = ((2000 - 1980) << 9) | (1 << 5) | 1
|
|
3591
|
-
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
3592
|
-
let value = index
|
|
3593
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
3594
|
-
value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1)
|
|
3595
|
-
}
|
|
3596
|
-
return value >>> 0
|
|
3597
|
-
})
|
|
3598
|
-
|
|
3599
|
-
function crc32(bytes: Buffer): number {
|
|
3600
|
-
let crc = 0xffffffff
|
|
3601
|
-
for (const byte of bytes) {
|
|
3602
|
-
crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8)
|
|
3603
|
-
}
|
|
3604
|
-
return (crc ^ 0xffffffff) >>> 0
|
|
3605
|
-
}
|
|
3606
|
-
|
|
3607
|
-
function createStoredZip(entries: Array<{ path: string; data: Buffer }>): Buffer {
|
|
3608
|
-
const localParts: Buffer[] = []
|
|
3609
|
-
const centralParts: Buffer[] = []
|
|
3610
|
-
let offset = 0
|
|
3611
|
-
|
|
3612
|
-
for (const entry of entries) {
|
|
3613
|
-
const name = Buffer.from(entry.path, 'utf8')
|
|
3614
|
-
const crc = crc32(entry.data)
|
|
3615
|
-
const localHeader = Buffer.alloc(30)
|
|
3616
|
-
localHeader.writeUInt32LE(0x04034b50, 0)
|
|
3617
|
-
localHeader.writeUInt16LE(10, 4)
|
|
3618
|
-
localHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 6)
|
|
3619
|
-
localHeader.writeUInt16LE(0, 8)
|
|
3620
|
-
localHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 10)
|
|
3621
|
-
localHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 12)
|
|
3622
|
-
localHeader.writeUInt32LE(crc, 14)
|
|
3623
|
-
localHeader.writeUInt32LE(entry.data.length, 18)
|
|
3624
|
-
localHeader.writeUInt32LE(entry.data.length, 22)
|
|
3625
|
-
localHeader.writeUInt16LE(name.length, 26)
|
|
3626
|
-
localHeader.writeUInt16LE(0, 28)
|
|
3627
|
-
|
|
3628
|
-
localParts.push(localHeader, name, entry.data)
|
|
3629
|
-
|
|
3630
|
-
const centralHeader = Buffer.alloc(46)
|
|
3631
|
-
centralHeader.writeUInt32LE(0x02014b50, 0)
|
|
3632
|
-
centralHeader.writeUInt16LE(20, 4)
|
|
3633
|
-
centralHeader.writeUInt16LE(10, 6)
|
|
3634
|
-
centralHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 8)
|
|
3635
|
-
centralHeader.writeUInt16LE(0, 10)
|
|
3636
|
-
centralHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 12)
|
|
3637
|
-
centralHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 14)
|
|
3638
|
-
centralHeader.writeUInt32LE(crc, 16)
|
|
3639
|
-
centralHeader.writeUInt32LE(entry.data.length, 20)
|
|
3640
|
-
centralHeader.writeUInt32LE(entry.data.length, 24)
|
|
3641
|
-
centralHeader.writeUInt16LE(name.length, 28)
|
|
3642
|
-
centralHeader.writeUInt16LE(0, 30)
|
|
3643
|
-
centralHeader.writeUInt16LE(0, 32)
|
|
3644
|
-
centralHeader.writeUInt16LE(0, 34)
|
|
3645
|
-
centralHeader.writeUInt16LE(0, 36)
|
|
3646
|
-
centralHeader.writeUInt32LE(0, 38)
|
|
3647
|
-
centralHeader.writeUInt32LE(offset, 42)
|
|
3648
|
-
centralParts.push(centralHeader, name)
|
|
3649
|
-
|
|
3650
|
-
offset += localHeader.length + name.length + entry.data.length
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
|
-
const centralDirectoryOffset = offset
|
|
3654
|
-
const centralDirectory = Buffer.concat(centralParts)
|
|
3655
|
-
const endOfCentralDirectory = Buffer.alloc(22)
|
|
3656
|
-
endOfCentralDirectory.writeUInt32LE(0x06054b50, 0)
|
|
3657
|
-
endOfCentralDirectory.writeUInt16LE(0, 4)
|
|
3658
|
-
endOfCentralDirectory.writeUInt16LE(0, 6)
|
|
3659
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 8)
|
|
3660
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 10)
|
|
3661
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12)
|
|
3662
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectoryOffset, 16)
|
|
3663
|
-
endOfCentralDirectory.writeUInt16LE(0, 20)
|
|
3664
|
-
|
|
3665
|
-
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory])
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
|
-
function zipDirectoryToBase64(dirArg: string): { zipBase64: string; contentSha256: string } {
|
|
3669
|
-
const sourceDir = expandHomePath(dirArg)
|
|
3670
|
-
try {
|
|
3671
|
-
const stats = statSync(sourceDir)
|
|
3672
|
-
if (!stats.isDirectory()) {
|
|
3673
|
-
throw new Error(`${sourceDir} is not a directory`)
|
|
3674
|
-
}
|
|
3675
|
-
} catch (error: any) {
|
|
3676
|
-
if (error?.code === 'ENOENT') {
|
|
3677
|
-
throw new Error(`Directory not found: ${sourceDir}`)
|
|
3678
|
-
}
|
|
3679
|
-
throw error
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
|
-
const files = collectArtifactFiles(sourceDir)
|
|
3683
|
-
if (files.length === 0) {
|
|
3684
|
-
throw new Error(`Directory contains no artifact files: ${sourceDir}`)
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
const bytes = createStoredZip(files.map(relativePath => ({
|
|
3688
|
-
path: relativePath,
|
|
3689
|
-
data: readFileSync(join(sourceDir, relativePath)),
|
|
3690
|
-
})))
|
|
3691
|
-
return {
|
|
3692
|
-
zipBase64: bytes.toString('base64'),
|
|
3693
|
-
contentSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
|
|
3697
4193
|
function readManifestFile(dirArg: string): Record<string, unknown> {
|
|
3698
4194
|
return readJsonFile(join(expandHomePath(dirArg), 'manifest.json'))
|
|
3699
4195
|
}
|
|
@@ -3909,13 +4405,47 @@ function parseCommaSeparated(value: string | null): string[] {
|
|
|
3909
4405
|
.filter(Boolean)
|
|
3910
4406
|
}
|
|
3911
4407
|
|
|
3912
|
-
function readAssignmentManifest(path: string | null): ReturnType<typeof
|
|
4408
|
+
function readAssignmentManifest(path: string | null): ReturnType<typeof readAssignmentManifestJsonlFile> | null {
|
|
3913
4409
|
if (!path) {
|
|
3914
4410
|
return null
|
|
3915
4411
|
}
|
|
3916
4412
|
|
|
3917
|
-
|
|
3918
|
-
|
|
4413
|
+
return readAssignmentManifestJsonlFile(expandHomePath(path))
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
function readOptionalTaskTextArg(
|
|
4417
|
+
valueFlag: string,
|
|
4418
|
+
fileFlag: string
|
|
4419
|
+
): string | undefined {
|
|
4420
|
+
const value = getArg(valueFlag)
|
|
4421
|
+
const file = getArg(fileFlag)
|
|
4422
|
+
rejectDashPrefixedOptionValue(valueFlag, value)
|
|
4423
|
+
rejectDashPrefixedOptionValue(fileFlag, file)
|
|
4424
|
+
|
|
4425
|
+
if (value !== null && file !== null) {
|
|
4426
|
+
throw new Error(`Use either ${valueFlag} or ${fileFlag}, not both.`)
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
if (file !== null) {
|
|
4430
|
+
return readSourceFile(file)
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
return value ?? undefined
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
function readPositiveIntegerFlag(name: string): number | undefined {
|
|
4437
|
+
const value = getArg(name)
|
|
4438
|
+
rejectDashPrefixedOptionValue(name, value)
|
|
4439
|
+
if (value === null) {
|
|
4440
|
+
return undefined
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
const parsed = Number(value)
|
|
4444
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
4445
|
+
throw new Error(`${name} must be a positive integer`)
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
return parsed
|
|
3919
4449
|
}
|
|
3920
4450
|
|
|
3921
4451
|
async function createTask() {
|
|
@@ -4058,6 +4588,155 @@ async function createTask() {
|
|
|
4058
4588
|
)
|
|
4059
4589
|
}
|
|
4060
4590
|
|
|
4591
|
+
async function updateTask() {
|
|
4592
|
+
const taskId = getArg('--task')
|
|
4593
|
+
rejectDashPrefixedOptionValue('--task', taskId)
|
|
4594
|
+
if (!taskId) {
|
|
4595
|
+
throw new Error('Usage: orizu tasks update --task <taskId> [--title <text>] [--description <text>|--description-file <path>] [--instructions <text>|--instructions-file <path>] [--dataset <datasetId>] [--app <appId> [--version <n>]] [--labels-per-item <n>] [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--json]')
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
const title = getArg('--title')
|
|
4599
|
+
const description = readOptionalTaskTextArg('--description', '--description-file')
|
|
4600
|
+
const instructions = readOptionalTaskTextArg('--instructions', '--instructions-file')
|
|
4601
|
+
const datasetId = getArg('--dataset')
|
|
4602
|
+
const appId = getArg('--app')
|
|
4603
|
+
const versionNum = readPositiveIntegerFlag('--version')
|
|
4604
|
+
const requiredAssignmentsPerRow = readPositiveIntegerFlag('--labels-per-item')
|
|
4605
|
+
const assignees = parseCommaSeparated(getArg('--assignees'))
|
|
4606
|
+
const assignmentFile = getArg('--assignment-file')
|
|
4607
|
+
|
|
4608
|
+
rejectDashPrefixedOptionValue('--title', title)
|
|
4609
|
+
rejectDashPrefixedOptionValue('--dataset', datasetId)
|
|
4610
|
+
rejectDashPrefixedOptionValue('--app', appId)
|
|
4611
|
+
rejectDashPrefixedOptionValue('--assignment-file', assignmentFile)
|
|
4612
|
+
|
|
4613
|
+
if (title !== null && title.trim().length === 0) {
|
|
4614
|
+
throw new Error('--title must be a non-empty string')
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4617
|
+
if (assignmentFile && assignees.length > 0) {
|
|
4618
|
+
throw new Error('Use either --assignment-file or --assignees, not both.')
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
if (hasArg('--assignees') && assignees.length === 0) {
|
|
4622
|
+
throw new Error('--assignees requires at least one user ID or email')
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
if (versionNum !== undefined && !appId) {
|
|
4626
|
+
throw new Error('--version requires --app')
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile)
|
|
4630
|
+
const requestBody: Record<string, unknown> = {}
|
|
4631
|
+
if (title !== null) requestBody.title = title.trim()
|
|
4632
|
+
if (description !== undefined) requestBody.description = description
|
|
4633
|
+
if (instructions !== undefined) requestBody.instructions = instructions
|
|
4634
|
+
if (datasetId !== null) requestBody.datasetId = datasetId
|
|
4635
|
+
if (appId !== null) requestBody.appId = appId
|
|
4636
|
+
if (versionNum !== undefined) requestBody.versionNum = versionNum
|
|
4637
|
+
if (requiredAssignmentsPerRow !== undefined) {
|
|
4638
|
+
requestBody.requiredAssignmentsPerRow = requiredAssignmentsPerRow
|
|
4639
|
+
}
|
|
4640
|
+
if (explicitAssignments) {
|
|
4641
|
+
requestBody.explicitAssignments = explicitAssignments
|
|
4642
|
+
} else if (hasArg('--assignees')) {
|
|
4643
|
+
requestBody.memberIds = assignees
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
if (Object.keys(requestBody).length === 0) {
|
|
4647
|
+
throw new Error('Usage: orizu tasks update --task <taskId> [--title <text>] [--description <text>|--description-file <path>] [--instructions <text>|--instructions-file <path>] [--dataset <datasetId>] [--app <appId> [--version <n>]] [--labels-per-item <n>] [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--json]')
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4650
|
+
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}`, {
|
|
4651
|
+
method: 'PATCH',
|
|
4652
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4653
|
+
body: JSON.stringify(requestBody),
|
|
4654
|
+
})
|
|
4655
|
+
|
|
4656
|
+
if (!response.ok) {
|
|
4657
|
+
throw new Error(`Failed to update task: ${await extractErrorMessage(response)}`)
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
const data = await parseJsonResponse<{
|
|
4661
|
+
task: {
|
|
4662
|
+
id: string
|
|
4663
|
+
title?: string | null
|
|
4664
|
+
status?: string | null
|
|
4665
|
+
dataset_id?: string | null
|
|
4666
|
+
app_id?: string | null
|
|
4667
|
+
version_id?: string | null
|
|
4668
|
+
required_assignments_per_row?: number | null
|
|
4669
|
+
}
|
|
4670
|
+
assignmentMode?: string
|
|
4671
|
+
assignmentsCreated?: number
|
|
4672
|
+
}>(response, 'Task update')
|
|
4673
|
+
|
|
4674
|
+
if (hasJsonFlag()) {
|
|
4675
|
+
printJson({
|
|
4676
|
+
taskId: data.task.id,
|
|
4677
|
+
title: data.task.title ?? null,
|
|
4678
|
+
status: data.task.status ?? null,
|
|
4679
|
+
datasetId: data.task.dataset_id ?? null,
|
|
4680
|
+
appId: data.task.app_id ?? null,
|
|
4681
|
+
versionId: data.task.version_id ?? null,
|
|
4682
|
+
requiredAssignmentsPerRow: data.task.required_assignments_per_row ?? null,
|
|
4683
|
+
...(data.assignmentMode ? { assignmentMode: data.assignmentMode } : {}),
|
|
4684
|
+
...(data.assignmentsCreated !== undefined
|
|
4685
|
+
? { assignmentsCreated: data.assignmentsCreated }
|
|
4686
|
+
: {}),
|
|
4687
|
+
})
|
|
4688
|
+
return
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
printLine(
|
|
4692
|
+
`Updated draft task ${sanitizeTerminalText(data.task.title || data.task.id)} (${sanitizeTerminalText(data.task.id)})` +
|
|
4693
|
+
(data.task.status ? ` [${sanitizeTerminalText(data.task.status)}]` : '') +
|
|
4694
|
+
(data.task.dataset_id ? `\n Dataset ID: ${sanitizeTerminalText(data.task.dataset_id)}` : '') +
|
|
4695
|
+
(data.task.version_id ? `\n Version ID: ${sanitizeTerminalText(data.task.version_id)}` : '') +
|
|
4696
|
+
(data.task.required_assignments_per_row
|
|
4697
|
+
? `\n Labels/row: ${data.task.required_assignments_per_row}`
|
|
4698
|
+
: '') +
|
|
4699
|
+
(data.assignmentMode ? `\n Mode: ${sanitizeTerminalText(data.assignmentMode === 'custom' ? 'custom row map' : 'auto distribute')}` : '') +
|
|
4700
|
+
(data.assignmentsCreated !== undefined ? `\n Assignments: ${data.assignmentsCreated}` : '')
|
|
4701
|
+
)
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
async function discardTask() {
|
|
4705
|
+
const taskId = getArg('--task')
|
|
4706
|
+
rejectDashPrefixedOptionValue('--task', taskId)
|
|
4707
|
+
if (!taskId) {
|
|
4708
|
+
throw new Error('Usage: orizu tasks discard --task <taskId> [--yes] [--json]')
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
if (!hasArg('--yes')) {
|
|
4712
|
+
if (!isInteractiveTerminal()) {
|
|
4713
|
+
throw new Error('Refusing to discard a draft in non-interactive mode without --yes')
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
const confirmed = await askYesNo(`Discard draft task ${taskId}? This cannot be undone.`, false)
|
|
4717
|
+
if (!confirmed) {
|
|
4718
|
+
printLine('Discard cancelled.')
|
|
4719
|
+
return
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}`, {
|
|
4724
|
+
method: 'DELETE',
|
|
4725
|
+
})
|
|
4726
|
+
|
|
4727
|
+
if (!response.ok) {
|
|
4728
|
+
throw new Error(`Failed to discard draft task: ${await extractErrorMessage(response)}`)
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
const data = await parseJsonResponse<{ success: boolean }>(response, 'Task discard')
|
|
4732
|
+
if (hasJsonFlag()) {
|
|
4733
|
+
printJson({ taskId, discarded: data.success })
|
|
4734
|
+
return
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
printLine(`Discarded draft task ${sanitizeTerminalText(taskId)}.`)
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4061
4740
|
async function publishTask() {
|
|
4062
4741
|
const taskId = getArg('--task')
|
|
4063
4742
|
const assignees = parseCommaSeparated(getArg('--assignees'))
|
|
@@ -4236,11 +4915,12 @@ async function taskStatus() {
|
|
|
4236
4915
|
printTaskStatusSummary(data)
|
|
4237
4916
|
}
|
|
4238
4917
|
|
|
4239
|
-
async function updateTaskStatus(targetStatus: 'paused' | 'active') {
|
|
4918
|
+
async function updateTaskStatus(targetStatus: 'paused' | 'active' | 'completed') {
|
|
4240
4919
|
const taskId = getArg('--task')
|
|
4920
|
+
const labels = TASK_STATUS_ACTION_LABELS[targetStatus]
|
|
4921
|
+
|
|
4241
4922
|
if (!taskId) {
|
|
4242
|
-
|
|
4243
|
-
throw new Error(`Usage: orizu tasks ${verb} --task <taskId>`)
|
|
4923
|
+
throw new Error(`Usage: orizu tasks ${labels.verb} --task <taskId>`)
|
|
4244
4924
|
}
|
|
4245
4925
|
|
|
4246
4926
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`, {
|
|
@@ -4250,8 +4930,7 @@ async function updateTaskStatus(targetStatus: 'paused' | 'active') {
|
|
|
4250
4930
|
})
|
|
4251
4931
|
|
|
4252
4932
|
if (!response.ok) {
|
|
4253
|
-
|
|
4254
|
-
throw new Error(`Failed to ${verb} task: ${await response.text()}`)
|
|
4933
|
+
throw new Error(`Failed to ${labels.verb} task: ${await response.text()}`)
|
|
4255
4934
|
}
|
|
4256
4935
|
|
|
4257
4936
|
const data = await parseJsonResponse<{ task: { id: string; status: string } }>(response, 'Task status update')
|
|
@@ -4259,8 +4938,7 @@ async function updateTaskStatus(targetStatus: 'paused' | 'active') {
|
|
|
4259
4938
|
printJson({ task: data.task })
|
|
4260
4939
|
return
|
|
4261
4940
|
}
|
|
4262
|
-
|
|
4263
|
-
printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`)
|
|
4941
|
+
printLine(`${labels.pastTense} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`)
|
|
4264
4942
|
}
|
|
4265
4943
|
|
|
4266
4944
|
async function setTaskReport() {
|
|
@@ -4523,10 +5201,10 @@ async function changeTeamMemberRole() {
|
|
|
4523
5201
|
const email = getArg('--email')
|
|
4524
5202
|
const role = getArg('--role')
|
|
4525
5203
|
if (!teamSlug || !email || !role) {
|
|
4526
|
-
throw new Error('Usage: orizu teams members role --team <teamSlug> --email <email> --role <admin|
|
|
5204
|
+
throw new Error('Usage: orizu teams members role --team <teamSlug> --email <email> --role <admin|curator|judge>')
|
|
4527
5205
|
}
|
|
4528
|
-
if (!['admin', '
|
|
4529
|
-
throw new Error('role must be one of: admin,
|
|
5206
|
+
if (!['admin', 'curator', 'judge'].includes(role)) {
|
|
5207
|
+
throw new Error('role must be one of: admin, curator, judge')
|
|
4530
5208
|
}
|
|
4531
5209
|
|
|
4532
5210
|
const members = await fetchTeamMembers(teamSlug)
|
|
@@ -5508,12 +6186,16 @@ async function runnersExec() {
|
|
|
5508
6186
|
}
|
|
5509
6187
|
|
|
5510
6188
|
const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8')) as Record<string, unknown>
|
|
6189
|
+
// Spread runner output first: the scorer/prompt context identity fields
|
|
6190
|
+
// (row_id, version ids, metric_key) are authoritative, never runner-overridable.
|
|
5511
6191
|
resultLines.push(JSON.stringify({
|
|
6192
|
+
...runnerOutput,
|
|
5512
6193
|
row_id: row.id,
|
|
5513
6194
|
prompt_version_id: context.prompt.promptVersionId,
|
|
5514
6195
|
runner_version_id: context.prompt.runnerVersionId,
|
|
5515
|
-
...(scorerVersion
|
|
5516
|
-
|
|
6196
|
+
...(scorerVersion
|
|
6197
|
+
? { scorer_version_id: context.scorer?.versionId || scorerVersion, metric_key: context.scorer?.metricKey || 'score' }
|
|
6198
|
+
: {}),
|
|
5517
6199
|
}))
|
|
5518
6200
|
} finally {
|
|
5519
6201
|
rmSync(tempDir, { recursive: true, force: true })
|
|
@@ -5879,6 +6561,16 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
5879
6561
|
return
|
|
5880
6562
|
}
|
|
5881
6563
|
|
|
6564
|
+
if (command === 'tasks' && subcommand === 'update') {
|
|
6565
|
+
await updateTask()
|
|
6566
|
+
return
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6569
|
+
if (command === 'tasks' && subcommand === 'discard') {
|
|
6570
|
+
await discardTask()
|
|
6571
|
+
return
|
|
6572
|
+
}
|
|
6573
|
+
|
|
5882
6574
|
if (command === 'tasks' && subcommand === 'publish') {
|
|
5883
6575
|
await publishTask()
|
|
5884
6576
|
return
|
|
@@ -5903,15 +6595,9 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
5903
6595
|
return
|
|
5904
6596
|
}
|
|
5905
6597
|
|
|
5906
|
-
if (command === 'tasks' && subcommand === 'pause') {
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
}
|
|
5910
|
-
|
|
5911
|
-
if (command === 'tasks' && subcommand === 'unpause') {
|
|
5912
|
-
await updateTaskStatus('active')
|
|
5913
|
-
return
|
|
5914
|
-
}
|
|
6598
|
+
if (command === 'tasks' && subcommand === 'pause') { await updateTaskStatus('paused'); return }
|
|
6599
|
+
if (command === 'tasks' && subcommand === 'unpause') { await updateTaskStatus('active'); return }
|
|
6600
|
+
if (command === 'tasks' && subcommand === 'complete') { await updateTaskStatus('completed'); return }
|
|
5915
6601
|
|
|
5916
6602
|
if (command === 'datasets' && subcommand === 'upload') {
|
|
5917
6603
|
await uploadDataset()
|
|
@@ -5927,55 +6613,60 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
5927
6613
|
await downloadDataset()
|
|
5928
6614
|
return
|
|
5929
6615
|
}
|
|
5930
|
-
|
|
5931
6616
|
if (command === 'datasets' && subcommand === 'append') {
|
|
5932
6617
|
await appendDatasetRows()
|
|
5933
6618
|
return
|
|
5934
6619
|
}
|
|
5935
|
-
|
|
5936
6620
|
const datasetsAction = cliArgs[2]
|
|
5937
6621
|
if (command === 'datasets' && subcommand === 'readme' && datasetsAction === 'set') {
|
|
5938
6622
|
await setDatasetReadme()
|
|
5939
6623
|
return
|
|
5940
6624
|
}
|
|
5941
|
-
|
|
5942
6625
|
if (command === 'datasets' && subcommand === 'versions' && datasetsAction === 'create') {
|
|
5943
6626
|
await createDatasetVersion()
|
|
5944
6627
|
return
|
|
5945
6628
|
}
|
|
5946
|
-
|
|
5947
6629
|
if (command === 'datasets' && subcommand === 'splits' && datasetsAction === 'create') {
|
|
5948
6630
|
await createDatasetSplitSet()
|
|
5949
6631
|
return
|
|
5950
6632
|
}
|
|
5951
|
-
|
|
5952
6633
|
if (command === 'datasets' && subcommand === 'edit-rows') {
|
|
5953
6634
|
await editDatasetRows()
|
|
5954
6635
|
return
|
|
5955
6636
|
}
|
|
5956
|
-
|
|
5957
6637
|
if (command === 'datasets' && subcommand === 'delete-rows') {
|
|
5958
6638
|
await deleteDatasetRows()
|
|
5959
6639
|
return
|
|
5960
6640
|
}
|
|
5961
|
-
|
|
5962
6641
|
if (command === 'datasets' && subcommand === 'delete') {
|
|
5963
6642
|
await deleteDataset()
|
|
5964
6643
|
return
|
|
5965
6644
|
}
|
|
5966
|
-
|
|
5967
|
-
if (command === 'datasets' && subcommand === '
|
|
5968
|
-
|
|
6645
|
+
if (command === 'datasets' && subcommand === 'lock') { await lockDataset(); return }
|
|
6646
|
+
if (command === 'datasets' && subcommand === 'clone') { await cloneDataset(); return }
|
|
6647
|
+
if (command === 'tasks' && subcommand === 'export') { await downloadAnnotations(); return }
|
|
6648
|
+
if (command === 'internal' || (command === 'session' && subcommand === 'start' && hasArg('--hosted'))) { process.exitCode = await hostedCommand(cliArgs, { json: hasJsonFlag(), print: printLine, printErr: printError }); return }
|
|
6649
|
+
if (command === 'session' || command === 'run') {
|
|
6650
|
+
process.exitCode = await workbenchCommand(cliArgs, { json: hasJsonFlag(), print: printLine })
|
|
5969
6651
|
return
|
|
5970
6652
|
}
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
await cloneDataset()
|
|
6653
|
+
if (command === 'workspace') {
|
|
6654
|
+
process.exitCode = await workspaceSyncCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine })
|
|
5974
6655
|
return
|
|
5975
6656
|
}
|
|
5976
|
-
|
|
5977
|
-
if (command === '
|
|
5978
|
-
|
|
6657
|
+
if (command === 'git-credential') { process.exitCode = await runGitCredential(subcommand ?? 'get', { stdin: readFileSync(0, 'utf8'), cwd: process.cwd(), print: printLine, printErr: printError }); return }
|
|
6658
|
+
if (command === 'github' && subcommand === 'link') { const noInput = hasArg('--no-input') || hasArg('--non-interactive') || !isInteractiveTerminal(); const team = getOptionalArgValue('--team') || (await resolveSetupTeam(null, noInput, false)).slug; await runGithubLink(team, { print: printLine, fetcher: authedFetch, openUrl: openInBrowser, confirm: (org) => askYesNo(`Connect this team to GitHub org '${org}'?`, true) }); return }
|
|
6659
|
+
if (command === 'connectors' || command === 'manifests') {
|
|
6660
|
+
const moduleCommand = command === 'connectors' ? connectorsCommand : manifestsCommand
|
|
6661
|
+
process.exitCode = await moduleCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, resolveProjectSlug })
|
|
6662
|
+
return
|
|
6663
|
+
}
|
|
6664
|
+
if (command === 'team' && (subcommand === 'kill-agents' || subcommand === 'release-agents')) {
|
|
6665
|
+
process.exitCode = await killSwitchCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, printErr: printError })
|
|
6666
|
+
return
|
|
6667
|
+
}
|
|
6668
|
+
if (command === 'team' && subcommand === 'egress-allowlist') {
|
|
6669
|
+
process.exitCode = await egressAllowlistCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, printErr: printError })
|
|
5979
6670
|
return
|
|
5980
6671
|
}
|
|
5981
6672
|
|
|
@@ -5998,7 +6689,7 @@ function isCliEntrypoint(): boolean {
|
|
|
5998
6689
|
|
|
5999
6690
|
if (isCliEntrypoint()) {
|
|
6000
6691
|
main().catch(error => {
|
|
6001
|
-
|
|
6692
|
+
printError(error instanceof Error ? error.message : 'Unknown error')
|
|
6002
6693
|
process.exit(1)
|
|
6003
6694
|
})
|
|
6004
6695
|
}
|