orizu 0.5.24 → 0.5.26
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 +23 -3
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +279 -59
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +5 -2
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +239 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -213
- package/dist/keyboard-select.d.ts +7 -0
- package/dist/keyboard-select.d.ts.map +1 -0
- package/dist/keyboard-select.js +72 -0
- package/dist/logout-diagnostic.d.ts +4 -0
- package/dist/logout-diagnostic.d.ts.map +1 -0
- package/dist/logout-diagnostic.js +26 -0
- package/dist/setup-account.d.ts +11 -0
- package/dist/setup-account.d.ts.map +1 -0
- package/dist/setup-account.js +106 -0
- package/dist/setup-onboarding.d.ts +27 -1
- package/dist/setup-onboarding.d.ts.map +1 -1
- package/dist/setup-onboarding.js +95 -11
- package/dist/setup-summary.d.ts +12 -0
- package/dist/setup-summary.d.ts.map +1 -0
- package/dist/setup-summary.js +56 -0
- package/package.json +1 -1
- package/src/credentials.ts +326 -57
- package/src/help.ts +7 -2
- package/src/http.ts +325 -20
- package/src/index.ts +193 -279
- package/src/keyboard-select.ts +97 -0
- package/src/logout-diagnostic.ts +39 -0
- package/src/setup-account.ts +129 -0
- package/src/setup-onboarding.ts +133 -10
- package/src/setup-summary.ts +77 -0
- package/vendor/skills/orizu/SKILL.md +51 -111
- package/vendor/skills/orizu/flows/expand.md +27 -0
- package/vendor/skills/orizu/flows/first-win.md +31 -0
- package/vendor/skills/orizu/flows/onboard.md +31 -0
- package/vendor/skills/orizu/flows/promote.md +33 -0
- package/vendor/skills/orizu/flows/recurse.md +23 -0
- package/vendor/skills/orizu/flows/triage.md +37 -0
- package/vendor/skills/orizu/references/assess-and-plan.md +5 -5
- package/vendor/skills/orizu/references/authority-map.md +20 -0
- package/vendor/skills/orizu/references/building-apps.md +7 -1
- package/vendor/skills/orizu/references/building-judges.md +15 -16
- package/vendor/skills/orizu/references/cli-reference.md +17 -8
- package/vendor/skills/orizu/references/dataset-design.md +16 -6
- package/vendor/skills/orizu/references/eval-strategy.md +18 -15
- package/vendor/skills/orizu/references/instructions-after-prompts.md +2 -2
- package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +10 -6
- package/vendor/skills/orizu/references/optimization-reports.md +11 -11
- package/vendor/skills/orizu/references/optimization-with-gepa.md +4 -2
- package/vendor/skills/orizu/references/primer.md +18 -22
- package/vendor/skills/orizu/references/prompt-control-plane.md +5 -5
- package/vendor/skills/orizu/references/vocabulary.md +27 -0
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'url'
|
|
|
5
5
|
import { createServer } from 'http'
|
|
6
6
|
import { gunzipSync, gzipSync } from 'zlib'
|
|
7
7
|
import {
|
|
8
|
-
existsSync,
|
|
9
8
|
mkdirSync,
|
|
10
9
|
mkdtempSync,
|
|
11
10
|
readFileSync,
|
|
@@ -15,7 +14,7 @@ import {
|
|
|
15
14
|
writeFileSync,
|
|
16
15
|
} from 'fs'
|
|
17
16
|
import { spawn, spawnSync } from 'child_process'
|
|
18
|
-
import {
|
|
17
|
+
import { tmpdir } from 'os'
|
|
19
18
|
import { createInterface } from 'readline/promises'
|
|
20
19
|
import { emitKeypressEvents } from 'readline'
|
|
21
20
|
import { stdin as input, stdout as output } from 'process'
|
|
@@ -33,6 +32,10 @@ import { parseDatasetReference } from './dataset-download.js'
|
|
|
33
32
|
import { editDatasetRows as runEditDatasetRows } from './dataset-edit-rows.js'
|
|
34
33
|
import { ensureDatasetUploadSnapshot } from './dataset-upload-snapshot.js'
|
|
35
34
|
import { extractErrorMessage } from './error-response.js'
|
|
35
|
+
import {
|
|
36
|
+
describeLogoutHttpFailure,
|
|
37
|
+
describeLogoutTransportFailure,
|
|
38
|
+
} from './logout-diagnostic.js'
|
|
36
39
|
import { parseGlobalFlags } from './global-flags.js'
|
|
37
40
|
import { shouldUseHeadlessLogin, waitForHeadlessAuthorization } from './headless-login.js'
|
|
38
41
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js'
|
|
@@ -41,10 +44,12 @@ import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js'
|
|
|
41
44
|
import {
|
|
42
45
|
assertSecureTokenTransport,
|
|
43
46
|
authedFetch,
|
|
47
|
+
captureAuthenticatedRequestContext,
|
|
44
48
|
credentialRequestRedirectPolicy,
|
|
45
49
|
getBaseUrl,
|
|
46
50
|
resolveLoginBaseUrl,
|
|
47
51
|
setGlobalFlags,
|
|
52
|
+
type AuthenticatedRequestContext,
|
|
48
53
|
} from './http.js'
|
|
49
54
|
import {
|
|
50
55
|
computeSkillContentHash,
|
|
@@ -57,7 +62,6 @@ import {
|
|
|
57
62
|
resolveSkillSource,
|
|
58
63
|
SKILL_INSTALL_AGENTS,
|
|
59
64
|
SKILL_INSTALL_TARGETS,
|
|
60
|
-
SkillInstallAgent,
|
|
61
65
|
SkillInstallMode,
|
|
62
66
|
SkillInstallScope,
|
|
63
67
|
SkillInstallTarget,
|
|
@@ -67,7 +71,20 @@ import {
|
|
|
67
71
|
import { renderBanner } from './banner.js'
|
|
68
72
|
import { formatTaskCreateError } from './task-create-error.js'
|
|
69
73
|
import { renderAgentSetupPrompt } from './setup-prompt.js'
|
|
70
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
AGENT_LAUNCH_SPECS,
|
|
76
|
+
detectedSetupSkillChoices,
|
|
77
|
+
findExecutable,
|
|
78
|
+
resolveSetupSkillHome,
|
|
79
|
+
setupLaunchPrompt,
|
|
80
|
+
setupNativeTargetForAgent,
|
|
81
|
+
validateSetupAgentArgs,
|
|
82
|
+
validateSetupFlagValues,
|
|
83
|
+
validateSetupSelectionArgs,
|
|
84
|
+
} from './setup-onboarding.js'
|
|
85
|
+
import { promptKeyboardSelect } from './keyboard-select.js'
|
|
86
|
+
import { confirmExistingSetupAccount, describeSetupAuthState } from './setup-account.js'
|
|
87
|
+
import { renderSetupSkillSummary, sanitizeSetupHumanInlineText } from './setup-summary.js'
|
|
71
88
|
import { LoginResponse } from './types.js'
|
|
72
89
|
import {
|
|
73
90
|
getWorkspaceRoot,
|
|
@@ -666,24 +683,26 @@ async function promptSelect<T>(
|
|
|
666
683
|
}
|
|
667
684
|
}
|
|
668
685
|
|
|
669
|
-
|
|
670
|
-
|
|
686
|
+
type AuthenticatedRequest = AuthenticatedRequestContext['fetch']
|
|
687
|
+
async function fetchTeams(request: AuthenticatedRequest = authedFetch): Promise<Team[]> {
|
|
688
|
+
const response = await request('/api/cli/teams')
|
|
671
689
|
if (!response.ok) {
|
|
672
690
|
throw new Error(`Failed to fetch teams: ${await response.text()}`)
|
|
673
691
|
}
|
|
674
|
-
|
|
675
692
|
const data = await parseJsonResponse<{ teams: Team[] }>(response, 'Teams list')
|
|
676
693
|
return data.teams
|
|
677
694
|
}
|
|
678
695
|
|
|
679
|
-
async function fetchProjects(
|
|
696
|
+
async function fetchProjects(
|
|
697
|
+
teamSlug?: string,
|
|
698
|
+
request: AuthenticatedRequest = authedFetch
|
|
699
|
+
): Promise<Project[]> {
|
|
680
700
|
const normalizedTeamSlug = teamSlug ? normalizeSlugInput(teamSlug) : undefined
|
|
681
701
|
const query = normalizedTeamSlug ? `?teamSlug=${encodeURIComponent(normalizedTeamSlug)}` : ''
|
|
682
|
-
const response = await
|
|
702
|
+
const response = await request(`/api/cli/projects${query}`)
|
|
683
703
|
if (!response.ok) {
|
|
684
704
|
throw new Error(`Failed to fetch projects: ${await response.text()}`)
|
|
685
705
|
}
|
|
686
|
-
|
|
687
706
|
const data = await parseJsonResponse<{ projects: Project[] }>(response, 'Projects list')
|
|
688
707
|
return data.projects
|
|
689
708
|
}
|
|
@@ -736,32 +755,35 @@ async function fetchTeamMembers(teamSlug: string): Promise<TeamMember[]> {
|
|
|
736
755
|
return data.members
|
|
737
756
|
}
|
|
738
757
|
|
|
739
|
-
async function createTeamOnServer(
|
|
740
|
-
|
|
758
|
+
async function createTeamOnServer(
|
|
759
|
+
name: string,
|
|
760
|
+
request: AuthenticatedRequest = authedFetch
|
|
761
|
+
): Promise<Team> {
|
|
762
|
+
const response = await request('/api/cli/teams', {
|
|
741
763
|
method: 'POST',
|
|
742
764
|
headers: { 'Content-Type': 'application/json' },
|
|
743
765
|
body: JSON.stringify({ name }),
|
|
744
766
|
})
|
|
745
|
-
|
|
746
767
|
if (!response.ok) {
|
|
747
768
|
throw new Error(`Failed to create team: ${await response.text()}`)
|
|
748
769
|
}
|
|
749
|
-
|
|
750
770
|
const data = await parseJsonResponse<{ team: Team }>(response, 'Team create')
|
|
751
771
|
return data.team
|
|
752
772
|
}
|
|
753
773
|
|
|
754
|
-
async function createProjectOnServer(
|
|
755
|
-
|
|
774
|
+
async function createProjectOnServer(
|
|
775
|
+
teamSlug: string,
|
|
776
|
+
name: string,
|
|
777
|
+
request: AuthenticatedRequest = authedFetch
|
|
778
|
+
): Promise<Project> {
|
|
779
|
+
const response = await request('/api/cli/projects', {
|
|
756
780
|
method: 'POST',
|
|
757
781
|
headers: { 'Content-Type': 'application/json' },
|
|
758
782
|
body: JSON.stringify({ teamSlug, name }),
|
|
759
783
|
})
|
|
760
|
-
|
|
761
784
|
if (!response.ok) {
|
|
762
785
|
throw new Error(`Failed to create project: ${await response.text()}`)
|
|
763
786
|
}
|
|
764
|
-
|
|
765
787
|
const data = await parseJsonResponse<{
|
|
766
788
|
project: {
|
|
767
789
|
id: string
|
|
@@ -773,7 +795,6 @@ async function createProjectOnServer(teamSlug: string, name: string): Promise<Pr
|
|
|
773
795
|
role?: string
|
|
774
796
|
}
|
|
775
797
|
}>(response, 'Project create')
|
|
776
|
-
|
|
777
798
|
return {
|
|
778
799
|
id: data.project.id,
|
|
779
800
|
name: data.project.name,
|
|
@@ -1052,8 +1073,9 @@ function resolveAuthCallbackPort(): number {
|
|
|
1052
1073
|
return parsed
|
|
1053
1074
|
}
|
|
1054
1075
|
|
|
1055
|
-
async function login() {
|
|
1056
|
-
const baseUrl =
|
|
1076
|
+
async function login(baseUrlOverride?: string) {
|
|
1077
|
+
const baseUrl = baseUrlOverride
|
|
1078
|
+
?? (hasArg('--no-prompt-if-logged-in') ? getBaseUrl() : resolveLoginBaseUrl())
|
|
1057
1079
|
assertSecureTokenTransport(baseUrl)
|
|
1058
1080
|
|
|
1059
1081
|
// hasResolvableAuth (ALI-1090): an env bearer (ORIZU_TOKEN / ORIZU_TOKEN_FILE)
|
|
@@ -1184,7 +1206,6 @@ async function whoami() {
|
|
|
1184
1206
|
if (!response.ok) {
|
|
1185
1207
|
throw new Error(`whoami failed: ${await response.text()}`)
|
|
1186
1208
|
}
|
|
1187
|
-
|
|
1188
1209
|
const data = await response.json() as { user: { id: string; email: string | null } }
|
|
1189
1210
|
if (hasJsonFlag()) {
|
|
1190
1211
|
printJson({ user: data.user, server: getBaseUrl() })
|
|
@@ -1193,8 +1214,8 @@ async function whoami() {
|
|
|
1193
1214
|
printLine(sanitizeTerminalText(data.user.email ?? data.user.id))
|
|
1194
1215
|
}
|
|
1195
1216
|
|
|
1196
|
-
async function logout() {
|
|
1197
|
-
const baseUrl = getBaseUrl()
|
|
1217
|
+
async function logout(baseUrlOverride?: string) {
|
|
1218
|
+
const baseUrl = baseUrlOverride || getBaseUrl()
|
|
1198
1219
|
const credentials = getServerCredentials(baseUrl)
|
|
1199
1220
|
if (!credentials) {
|
|
1200
1221
|
if (hasJsonFlag()) {
|
|
@@ -1204,7 +1225,13 @@ async function logout() {
|
|
|
1204
1225
|
printLine(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`)
|
|
1205
1226
|
return
|
|
1206
1227
|
}
|
|
1207
|
-
|
|
1228
|
+
const authorizationToken = 'accessToken' in credentials
|
|
1229
|
+
? credentials.accessToken
|
|
1230
|
+
: credentials.apiKey
|
|
1231
|
+
const logoutSecrets = [
|
|
1232
|
+
authorizationToken,
|
|
1233
|
+
...('refreshToken' in credentials ? [credentials.refreshToken] : []),
|
|
1234
|
+
]
|
|
1208
1235
|
let remoteLogoutError: string | null = null
|
|
1209
1236
|
try {
|
|
1210
1237
|
assertSecureTokenTransport(baseUrl)
|
|
@@ -1212,22 +1239,19 @@ async function logout() {
|
|
|
1212
1239
|
method: 'POST',
|
|
1213
1240
|
headers: {
|
|
1214
1241
|
'Content-Type': 'application/json',
|
|
1215
|
-
Authorization: `Bearer ${
|
|
1242
|
+
Authorization: `Bearer ${authorizationToken}`,
|
|
1216
1243
|
},
|
|
1217
1244
|
body: 'refreshToken' in credentials
|
|
1218
1245
|
? JSON.stringify({ refreshToken: credentials.refreshToken })
|
|
1219
1246
|
: undefined,
|
|
1220
1247
|
...credentialRequestRedirectPolicy(),
|
|
1221
1248
|
})
|
|
1222
|
-
|
|
1223
1249
|
if (!response.ok) {
|
|
1224
|
-
remoteLogoutError =
|
|
1250
|
+
remoteLogoutError = await describeLogoutHttpFailure(response, logoutSecrets)
|
|
1225
1251
|
}
|
|
1226
1252
|
} catch (error) {
|
|
1227
|
-
|
|
1228
|
-
remoteLogoutError = sanitizeTerminalText(message).slice(0, 180)
|
|
1253
|
+
remoteLogoutError = describeLogoutTransportFailure(error, logoutSecrets)
|
|
1229
1254
|
}
|
|
1230
|
-
|
|
1231
1255
|
clearServerCredentials(baseUrl)
|
|
1232
1256
|
if (hasJsonFlag()) {
|
|
1233
1257
|
printJson({ status: 'logged-out', server: baseUrl, remoteLogoutError })
|
|
@@ -2754,87 +2778,6 @@ async function waitForAnyKeyOrEscape(message: string): Promise<boolean> {
|
|
|
2754
2778
|
})
|
|
2755
2779
|
}
|
|
2756
2780
|
|
|
2757
|
-
async function promptKeyboardSelect<T>(
|
|
2758
|
-
title: string,
|
|
2759
|
-
items: T[],
|
|
2760
|
-
label: (item: T, index: number) => string
|
|
2761
|
-
): Promise<T> {
|
|
2762
|
-
if (items.length === 0) {
|
|
2763
|
-
throw new Error(`No options available for ${title.toLowerCase()}`)
|
|
2764
|
-
}
|
|
2765
|
-
if (!isInteractiveTerminal()) {
|
|
2766
|
-
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`)
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
printLine(`\n${sanitizeTerminalText(title)}`)
|
|
2770
|
-
printLine('Use ↑/↓ to choose, Enter to confirm, or Esc to cancel.')
|
|
2771
|
-
|
|
2772
|
-
let selected = 0
|
|
2773
|
-
let rendered = false
|
|
2774
|
-
const rawInput = input as RawInput
|
|
2775
|
-
const wasRaw = Boolean(rawInput.isRaw)
|
|
2776
|
-
emitKeypressEvents(input)
|
|
2777
|
-
|
|
2778
|
-
function render() {
|
|
2779
|
-
if (rendered) {
|
|
2780
|
-
output.write(`\x1b[${items.length}A`)
|
|
2781
|
-
}
|
|
2782
|
-
items.forEach((item, index) => {
|
|
2783
|
-
output.write('\x1b[2K')
|
|
2784
|
-
const marker = index === selected ? '●' : '○'
|
|
2785
|
-
output.write(` ${marker} ${sanitizeTerminalText(label(item, index))}\n`)
|
|
2786
|
-
})
|
|
2787
|
-
rendered = true
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
render()
|
|
2791
|
-
output.write('\x1b[?25l')
|
|
2792
|
-
|
|
2793
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
2794
|
-
function cleanup() {
|
|
2795
|
-
input.off('keypress', onKeypress)
|
|
2796
|
-
if (rawInput.setRawMode && !wasRaw) {
|
|
2797
|
-
rawInput.setRawMode(false)
|
|
2798
|
-
}
|
|
2799
|
-
input.pause()
|
|
2800
|
-
output.write('\x1b[?25h')
|
|
2801
|
-
printLine('')
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
function onKeypress(_chunk: string, key: KeypressInfo) {
|
|
2805
|
-
if (key.ctrl && key.name === 'c') {
|
|
2806
|
-
cleanup()
|
|
2807
|
-
rejectPromise(new Error('Setup cancelled.'))
|
|
2808
|
-
return
|
|
2809
|
-
}
|
|
2810
|
-
if (key.name === 'escape') {
|
|
2811
|
-
cleanup()
|
|
2812
|
-
rejectPromise(new Error('Setup cancelled.'))
|
|
2813
|
-
return
|
|
2814
|
-
}
|
|
2815
|
-
if (key.name === 'up') {
|
|
2816
|
-
selected = (selected - 1 + items.length) % items.length
|
|
2817
|
-
render()
|
|
2818
|
-
return
|
|
2819
|
-
}
|
|
2820
|
-
if (key.name === 'down') {
|
|
2821
|
-
selected = (selected + 1) % items.length
|
|
2822
|
-
render()
|
|
2823
|
-
return
|
|
2824
|
-
}
|
|
2825
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
2826
|
-
const item = items[selected]
|
|
2827
|
-
cleanup()
|
|
2828
|
-
resolvePromise(item)
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
input.on('keypress', onKeypress)
|
|
2833
|
-
input.resume()
|
|
2834
|
-
rawInput.setRawMode?.(true)
|
|
2835
|
-
})
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
2781
|
async function promptKeyboardMultiSelect<T>(
|
|
2839
2782
|
title: string,
|
|
2840
2783
|
items: T[],
|
|
@@ -2984,14 +2927,14 @@ function writeWorkspaceValidationLog(
|
|
|
2984
2927
|
|
|
2985
2928
|
function printWorkspaceFindings(findings: { severity: string, code: string, path?: string, message: string }[]) {
|
|
2986
2929
|
if (findings.length === 0) {
|
|
2987
|
-
printLine('
|
|
2930
|
+
printLine('Validation passed.')
|
|
2988
2931
|
return
|
|
2989
2932
|
}
|
|
2990
2933
|
|
|
2991
|
-
printLine('
|
|
2934
|
+
printLine('Validation findings:')
|
|
2992
2935
|
for (const finding of findings) {
|
|
2993
2936
|
const path = finding.path ? ` ${finding.path}` : ''
|
|
2994
|
-
printLine(
|
|
2937
|
+
printLine(`- ${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`)
|
|
2995
2938
|
}
|
|
2996
2939
|
}
|
|
2997
2940
|
|
|
@@ -3367,15 +3310,6 @@ function skillsPathCommand() {
|
|
|
3367
3310
|
printLine(hasArg('--skill-md') ? source.skillMd : source.root)
|
|
3368
3311
|
}
|
|
3369
3312
|
|
|
3370
|
-
function describeSetupAuthState(): { state: 'signed-in' | 'signed-out', baseUrl: string } {
|
|
3371
|
-
const baseUrl = getBaseUrl()
|
|
3372
|
-
return {
|
|
3373
|
-
// ALI-1090: an env bearer (ORIZU_TOKEN / ORIZU_TOKEN_FILE) is signed-in too.
|
|
3374
|
-
state: hasResolvableAuth(baseUrl) ? 'signed-in' : 'signed-out',
|
|
3375
|
-
baseUrl,
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
|
|
3379
3313
|
type SetupTeamChoice =
|
|
3380
3314
|
| { kind: 'team', team: Team }
|
|
3381
3315
|
| { kind: 'create' }
|
|
@@ -3385,7 +3319,8 @@ async function resolveSetupTeam(
|
|
|
3385
3319
|
createTeamName: string | null,
|
|
3386
3320
|
noInput: boolean,
|
|
3387
3321
|
dryRun: boolean,
|
|
3388
|
-
workspaceTeamSlug: string | null
|
|
3322
|
+
workspaceTeamSlug: string | null,
|
|
3323
|
+
request: AuthenticatedRequest = authedFetch
|
|
3389
3324
|
): Promise<Team> {
|
|
3390
3325
|
if (teamSlugArg && createTeamName) {
|
|
3391
3326
|
throw new Error('Use either --team <slug> or --create-team <name>, not both.')
|
|
@@ -3397,14 +3332,12 @@ async function resolveSetupTeam(
|
|
|
3397
3332
|
if (workspaceTeamSlug) throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Create the new team from another directory.`)
|
|
3398
3333
|
const name = createTeamName.trim()
|
|
3399
3334
|
if (!name) throw new Error('--create-team requires a non-empty team name.')
|
|
3400
|
-
const team = await createTeamOnServer(name)
|
|
3401
|
-
printLine(`
|
|
3335
|
+
const team = await createTeamOnServer(name, request)
|
|
3336
|
+
printLine(`Created team ${sanitizeTerminalText(team.slug)}`)
|
|
3402
3337
|
return team
|
|
3403
3338
|
}
|
|
3404
|
-
|
|
3405
|
-
const teams = await fetchTeams()
|
|
3339
|
+
const teams = await fetchTeams(request)
|
|
3406
3340
|
const normalizedTeamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null
|
|
3407
|
-
|
|
3408
3341
|
if (normalizedTeamSlug) {
|
|
3409
3342
|
const team = teams.find(candidate => candidate.slug === normalizedTeamSlug)
|
|
3410
3343
|
if (!team) {
|
|
@@ -3412,11 +3345,9 @@ async function resolveSetupTeam(
|
|
|
3412
3345
|
}
|
|
3413
3346
|
return team
|
|
3414
3347
|
}
|
|
3415
|
-
|
|
3416
3348
|
if (noInput || dryRun) {
|
|
3417
3349
|
throw new Error('Non-interactive setup requires --team <slug> or --create-team <name>.')
|
|
3418
3350
|
}
|
|
3419
|
-
|
|
3420
3351
|
const choices: SetupTeamChoice[] = [
|
|
3421
3352
|
...teams.map(team => ({ kind: 'team' as const, team })),
|
|
3422
3353
|
...(teams.some(team => team.role === 'agent') ? [] : [{ kind: 'create' as const }]),
|
|
@@ -3428,18 +3359,16 @@ async function resolveSetupTeam(
|
|
|
3428
3359
|
? 'Create a new team'
|
|
3429
3360
|
: `${item.team.name} (${item.team.slug})`
|
|
3430
3361
|
)
|
|
3431
|
-
|
|
3432
3362
|
if (choice.kind === 'team') {
|
|
3433
3363
|
return choice.team
|
|
3434
3364
|
}
|
|
3435
|
-
|
|
3436
|
-
const name = await askText(' Team name?', '')
|
|
3365
|
+
const name = await askText('Team name?', '')
|
|
3437
3366
|
if (!name) {
|
|
3438
3367
|
throw new Error('Team name is required to create a team.')
|
|
3439
3368
|
}
|
|
3440
3369
|
if (workspaceTeamSlug) throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Create the new team from another directory.`)
|
|
3441
|
-
const team = await createTeamOnServer(name)
|
|
3442
|
-
printLine(`
|
|
3370
|
+
const team = await createTeamOnServer(name, request)
|
|
3371
|
+
printLine(`Created team ${sanitizeTerminalText(team.slug)}`)
|
|
3443
3372
|
return team
|
|
3444
3373
|
}
|
|
3445
3374
|
|
|
@@ -3456,7 +3385,8 @@ async function resolveSetupProjects(
|
|
|
3456
3385
|
projectSlugArg: string | null,
|
|
3457
3386
|
createProjectName: string | null,
|
|
3458
3387
|
noInput: boolean,
|
|
3459
|
-
dryRun: boolean
|
|
3388
|
+
dryRun: boolean,
|
|
3389
|
+
request: AuthenticatedRequest
|
|
3460
3390
|
): Promise<SetupProjectSelection> {
|
|
3461
3391
|
if (projectSlugArg && createProjectName) {
|
|
3462
3392
|
throw new Error('Use either --project <slug> or --create-project <name>, not both.')
|
|
@@ -3464,23 +3394,21 @@ async function resolveSetupProjects(
|
|
|
3464
3394
|
if ((noInput || dryRun) && !projectSlugArg && !createProjectName) {
|
|
3465
3395
|
throw new Error('Non-interactive setup requires --project <slug> or --create-project <name>.')
|
|
3466
3396
|
}
|
|
3467
|
-
|
|
3468
3397
|
const canCreateProject = team.role === 'admin' || team.role === 'agent'
|
|
3469
3398
|
if (createProjectName && !canCreateProject) {
|
|
3470
3399
|
throw new Error(`Only team admins can create projects in '${team.slug}'. Choose an existing project or ask a team admin to create one.`)
|
|
3471
3400
|
}
|
|
3472
|
-
const projects = await fetchProjects(team.slug)
|
|
3401
|
+
const projects = await fetchProjects(team.slug, request)
|
|
3473
3402
|
if (createProjectName) {
|
|
3474
3403
|
if (dryRun) {
|
|
3475
3404
|
throw new Error('--create-project cannot be used with --dry-run because setup cannot preview a server-generated project slug.')
|
|
3476
3405
|
}
|
|
3477
3406
|
const name = createProjectName.trim()
|
|
3478
3407
|
if (!name) throw new Error('--create-project requires a non-empty project name.')
|
|
3479
|
-
const selected = await createProjectOnServer(team.slug, name)
|
|
3480
|
-
printLine(`
|
|
3408
|
+
const selected = await createProjectOnServer(team.slug, name, request)
|
|
3409
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`)
|
|
3481
3410
|
return { selected, projects: [...projects, selected] }
|
|
3482
3411
|
}
|
|
3483
|
-
|
|
3484
3412
|
const normalizedProjectSlug = projectSlugArg ? normalizeSlugInput(projectSlugArg) : null
|
|
3485
3413
|
if (normalizedProjectSlug) {
|
|
3486
3414
|
const selected = projects.find(project => project.slug === normalizedProjectSlug)
|
|
@@ -3489,17 +3417,15 @@ async function resolveSetupProjects(
|
|
|
3489
3417
|
}
|
|
3490
3418
|
return { selected, projects }
|
|
3491
3419
|
}
|
|
3492
|
-
|
|
3493
3420
|
if (projects.length === 0) {
|
|
3494
3421
|
if (!canCreateProject) throw new Error(`Team '${team.slug}' has no accessible projects, and only team admins can create one.`)
|
|
3495
|
-
printLine(`
|
|
3496
|
-
const name = await askText('
|
|
3422
|
+
printLine(`Team '${sanitizeTerminalText(team.slug)}' has no projects yet. Let's create the first one.`)
|
|
3423
|
+
const name = await askText('Project name?', '')
|
|
3497
3424
|
if (!name) throw new Error('Project name is required to create a project.')
|
|
3498
|
-
const selected = await createProjectOnServer(team.slug, name)
|
|
3499
|
-
printLine(`
|
|
3425
|
+
const selected = await createProjectOnServer(team.slug, name, request)
|
|
3426
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`)
|
|
3500
3427
|
return { selected, projects: [selected] }
|
|
3501
3428
|
}
|
|
3502
|
-
|
|
3503
3429
|
const choices: SetupProjectChoice[] = [
|
|
3504
3430
|
...projects.map(project => ({ kind: 'project' as const, project })),
|
|
3505
3431
|
...(canCreateProject ? [{ kind: 'create' as const }] : []),
|
|
@@ -3514,11 +3440,10 @@ async function resolveSetupProjects(
|
|
|
3514
3440
|
if (choice.kind === 'project') {
|
|
3515
3441
|
return { selected: choice.project, projects }
|
|
3516
3442
|
}
|
|
3517
|
-
|
|
3518
|
-
const name = await askText(' Project name?', '')
|
|
3443
|
+
const name = await askText('Project name?', '')
|
|
3519
3444
|
if (!name) throw new Error('Project name is required to create a project.')
|
|
3520
|
-
const selected = await createProjectOnServer(team.slug, name)
|
|
3521
|
-
printLine(`
|
|
3445
|
+
const selected = await createProjectOnServer(team.slug, name, request)
|
|
3446
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`)
|
|
3522
3447
|
return { selected, projects: [...projects, selected] }
|
|
3523
3448
|
}
|
|
3524
3449
|
|
|
@@ -3534,47 +3459,6 @@ function formatProjectSetupProgress(count: number): string {
|
|
|
3534
3459
|
return `${count} project${count === 1 ? '' : 's'} being set up in workspace...`
|
|
3535
3460
|
}
|
|
3536
3461
|
|
|
3537
|
-
interface SetupSkillChoice {
|
|
3538
|
-
agent: SkillInstallAgent
|
|
3539
|
-
label: string
|
|
3540
|
-
target: SkillInstallTarget
|
|
3541
|
-
}
|
|
3542
|
-
|
|
3543
|
-
const SETUP_NATIVE_SKILL_CHOICES: ReadonlyArray<{
|
|
3544
|
-
agent: SkillInstallAgent
|
|
3545
|
-
label: string
|
|
3546
|
-
directory: string[]
|
|
3547
|
-
binary?: string
|
|
3548
|
-
}> = [
|
|
3549
|
-
{ agent: 'claude', label: 'Claude Code', directory: ['.claude'], binary: 'claude' },
|
|
3550
|
-
{ agent: 'devin', label: 'Devin', directory: ['.devin', 'skills'] },
|
|
3551
|
-
{ agent: 'droid', label: 'Droid', directory: ['.factory', 'skills'] },
|
|
3552
|
-
{ agent: 'grok', label: 'Grok Build', directory: ['.grok', 'skills'] },
|
|
3553
|
-
{ agent: 'windsurf', label: 'Windsurf', directory: ['.windsurf', 'skills'] },
|
|
3554
|
-
{ agent: 'opencode', label: 'OpenCode', directory: ['.config', 'opencode', 'skills'], binary: 'opencode' },
|
|
3555
|
-
]
|
|
3556
|
-
|
|
3557
|
-
function setupNativeTargetForAgent(agent: SkillInstallAgent): SkillInstallTarget | null {
|
|
3558
|
-
if (agent === 'codex' || agent === 'pi') return null
|
|
3559
|
-
return getTargetForAgent(agent, 'global')
|
|
3560
|
-
}
|
|
3561
|
-
function resolveSetupSkillHome(): string {
|
|
3562
|
-
return resolve(process.env.HOME || homedir())
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
function detectedSetupSkillChoices(homeDir: string): SetupSkillChoice[] {
|
|
3566
|
-
return SETUP_NATIVE_SKILL_CHOICES
|
|
3567
|
-
.filter(choice =>
|
|
3568
|
-
existsSync(join(homeDir, ...choice.directory))
|
|
3569
|
-
|| Boolean(choice.binary && findExecutable(choice.binary))
|
|
3570
|
-
)
|
|
3571
|
-
.map(choice => ({
|
|
3572
|
-
agent: choice.agent,
|
|
3573
|
-
label: choice.label,
|
|
3574
|
-
target: getTargetForAgent(choice.agent, 'global'),
|
|
3575
|
-
}))
|
|
3576
|
-
}
|
|
3577
|
-
|
|
3578
3462
|
function setupSkillTargetsFromArgs(): SkillInstallTarget[] {
|
|
3579
3463
|
const targets: SkillInstallTarget[] = []
|
|
3580
3464
|
for (const agent of getArgs('--agent')) {
|
|
@@ -3626,26 +3510,68 @@ async function setupCommand() {
|
|
|
3626
3510
|
if (workspaceManifest.state === 'invalid' && !validateOnly && !fix) throw new Error('The existing orizu.team.json is invalid or unreadable. Repair it before creating remote resources.')
|
|
3627
3511
|
const workspaceTeamSlug = workspaceManifest.state === 'valid' ? workspaceManifest.slug : null
|
|
3628
3512
|
if (createTeamArg !== null && workspaceTeamSlug) throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Create the new team from another directory.`)
|
|
3629
|
-
|
|
3630
3513
|
printBannerIfInteractive()
|
|
3631
3514
|
printLine('Orizu setup')
|
|
3632
3515
|
printLine('')
|
|
3633
|
-
|
|
3634
3516
|
// Step 1: login
|
|
3635
3517
|
printLine('Step 1: Login')
|
|
3636
3518
|
let auth = describeSetupAuthState()
|
|
3519
|
+
let setupAuthContext: AuthenticatedRequestContext | null = null
|
|
3637
3520
|
if (hasArg('--skip-login')) {
|
|
3638
|
-
printLine('
|
|
3521
|
+
printLine('Skipped (--skip-login).')
|
|
3639
3522
|
} else if (auth.state === 'signed-in') {
|
|
3640
|
-
|
|
3523
|
+
if (!validateOnly) {
|
|
3524
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun })
|
|
3525
|
+
}
|
|
3526
|
+
if (!noInput && !validateOnly) {
|
|
3527
|
+
const confirmation = await confirmExistingSetupAccount(
|
|
3528
|
+
setupAuthContext!,
|
|
3529
|
+
email => promptKeyboardSelect(
|
|
3530
|
+
'Confirm the account to use for setup',
|
|
3531
|
+
['continue', 'switch'] as const,
|
|
3532
|
+
item => item === 'continue'
|
|
3533
|
+
? `Continue as ${email}`
|
|
3534
|
+
: 'Log in with a different account'
|
|
3535
|
+
),
|
|
3536
|
+
async () => {
|
|
3537
|
+
const context = setupAuthContext!
|
|
3538
|
+
const logoutResult = await context.logout()
|
|
3539
|
+
if (logoutResult.remoteError) {
|
|
3540
|
+
const localOutcome = logoutResult.localCredentialCleared
|
|
3541
|
+
? 'Local credentials were cleared.'
|
|
3542
|
+
: 'Local credentials were not cleared because a newer login is active.'
|
|
3543
|
+
console.warn(
|
|
3544
|
+
`Warning: remote logout failed: ${logoutResult.remoteError} ${localOutcome}`
|
|
3545
|
+
)
|
|
3546
|
+
} else if (!logoutResult.localCredentialCleared) {
|
|
3547
|
+
console.warn('Warning: local credentials were not cleared because a newer login is active.')
|
|
3548
|
+
}
|
|
3549
|
+
printLine(logoutResult.localCredentialCleared
|
|
3550
|
+
? `Logged out from ${sanitizeTerminalText(context.baseUrl)}.`
|
|
3551
|
+
: `Kept the newer login for ${sanitizeTerminalText(context.baseUrl)}.`)
|
|
3552
|
+
await login(context.baseUrl)
|
|
3553
|
+
},
|
|
3554
|
+
{ dryRun }
|
|
3555
|
+
)
|
|
3556
|
+
if (confirmation === 'switched') {
|
|
3557
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun })
|
|
3558
|
+
}
|
|
3559
|
+
auth = { state: 'signed-in', baseUrl: setupAuthContext!.baseUrl }
|
|
3560
|
+
} else {
|
|
3561
|
+
printLine(`Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`)
|
|
3562
|
+
}
|
|
3641
3563
|
} else if (noInput || dryRun) {
|
|
3642
|
-
printLine('
|
|
3643
|
-
} else if (await waitForAnyKeyOrEscape('
|
|
3564
|
+
printLine('Not signed in. Run `orizu login` to authenticate.')
|
|
3565
|
+
} else if (await waitForAnyKeyOrEscape('Press any key to connect your account, or Esc to cancel.')) {
|
|
3644
3566
|
await login()
|
|
3645
3567
|
auth = describeSetupAuthState()
|
|
3646
3568
|
} else {
|
|
3647
3569
|
throw new Error('Setup cancelled.')
|
|
3648
3570
|
}
|
|
3571
|
+
if (auth.state === 'signed-in' && !validateOnly && !setupAuthContext) {
|
|
3572
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun })
|
|
3573
|
+
}
|
|
3574
|
+
if (setupAuthContext) auth = { state: 'signed-in', baseUrl: setupAuthContext.baseUrl }
|
|
3649
3575
|
const serverCreateRequested = createTeamArg !== null || createProjectArg !== null
|
|
3650
3576
|
if ((noInput || serverCreateRequested) && auth.state === 'signed-out' && !dryRun && !validateOnly && !fix) {
|
|
3651
3577
|
throw new Error(
|
|
@@ -3653,20 +3579,20 @@ async function setupCommand() {
|
|
|
3653
3579
|
)
|
|
3654
3580
|
}
|
|
3655
3581
|
printLine('')
|
|
3656
|
-
|
|
3657
3582
|
let setupTeam: Team | null = null
|
|
3658
3583
|
let selectedProject: Project | null = null
|
|
3659
3584
|
let setupProjects: Project[] = []
|
|
3660
3585
|
if (auth.state === 'signed-in' && !validateOnly) {
|
|
3661
|
-
|
|
3586
|
+
const setupRequest = setupAuthContext!.fetch.bind(setupAuthContext)
|
|
3587
|
+
setupTeam = await resolveSetupTeam(teamArg, createTeamArg, noInput, dryRun, workspaceTeamSlug, setupRequest)
|
|
3662
3588
|
if (workspaceTeamSlug && setupTeam.slug !== workspaceTeamSlug) {
|
|
3663
3589
|
throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Run setup in another directory to set up team '${setupTeam.slug}'.`)
|
|
3664
3590
|
}
|
|
3665
3591
|
if (fix) {
|
|
3666
|
-
setupProjects = await fetchProjects(setupTeam.slug)
|
|
3592
|
+
setupProjects = await fetchProjects(setupTeam.slug, setupRequest)
|
|
3667
3593
|
if (setupProjects.length === 0) throw new Error(`Team '${setupTeam.slug}' has no projects to restore.`)
|
|
3668
3594
|
} else {
|
|
3669
|
-
const selection = await resolveSetupProjects(setupTeam, projectArg, createProjectArg, noInput, dryRun)
|
|
3595
|
+
const selection = await resolveSetupProjects(setupTeam, projectArg, createProjectArg, noInput, dryRun, setupRequest)
|
|
3670
3596
|
selectedProject = selection.selected
|
|
3671
3597
|
setupProjects = selection.projects
|
|
3672
3598
|
printLine(`Selected project ${sanitizeTerminalText(`${setupTeam.slug}/${selectedProject.slug}`)}`)
|
|
@@ -3680,7 +3606,7 @@ async function setupCommand() {
|
|
|
3680
3606
|
let workspaceResult: ReturnType<typeof initOrizuWorkspace> | null = null
|
|
3681
3607
|
let validationLogPath: string | null = null
|
|
3682
3608
|
if (hasArg('--no-workspace') && !validateOnly && !fix) {
|
|
3683
|
-
printLine('
|
|
3609
|
+
printLine('Skipped (--no-workspace).')
|
|
3684
3610
|
} else {
|
|
3685
3611
|
const wantsWorkspace = validateOnly
|
|
3686
3612
|
|| fix
|
|
@@ -3693,26 +3619,24 @@ async function setupCommand() {
|
|
|
3693
3619
|
const projectSlug = projectArg
|
|
3694
3620
|
let teamId: string | null = null
|
|
3695
3621
|
let projects: WorkspaceProjectSeed[] | undefined
|
|
3696
|
-
|
|
3697
3622
|
if (setupTeam && !validateOnly) {
|
|
3698
3623
|
teamSlug = setupTeam.slug
|
|
3699
3624
|
teamId = setupTeam.id
|
|
3700
3625
|
projects = projectSeedsFromProjects(setupProjects)
|
|
3701
|
-
printLine(
|
|
3626
|
+
printLine(formatProjectSetupProgress(projects.length))
|
|
3702
3627
|
} else if (!teamSlug && !noInput && !validateOnly) {
|
|
3703
|
-
teamSlug = await askText('
|
|
3628
|
+
teamSlug = await askText('Team slug?', 'local-team')
|
|
3704
3629
|
}
|
|
3705
3630
|
if (auth.state !== 'signed-in' && (!teamSlug || !projectSlug) && noInput && hasArg('--workspace')) {
|
|
3706
|
-
printLine('
|
|
3631
|
+
printLine('Using starter local-only team/project. Pass --team with authentication to materialize server projects.')
|
|
3707
3632
|
}
|
|
3708
3633
|
if (projectSlug && auth.state !== 'signed-in') {
|
|
3709
3634
|
projects = [{ slug: projectSlug }]
|
|
3710
3635
|
}
|
|
3711
3636
|
if (auth.state !== 'signed-in' && !validateOnly) {
|
|
3712
3637
|
const localProjectCount = projects?.length || 1
|
|
3713
|
-
printLine(
|
|
3638
|
+
printLine(formatProjectSetupProgress(localProjectCount))
|
|
3714
3639
|
}
|
|
3715
|
-
|
|
3716
3640
|
const result = initOrizuWorkspace({
|
|
3717
3641
|
workspaceRoot,
|
|
3718
3642
|
teamSlug,
|
|
@@ -3732,12 +3656,12 @@ async function setupCommand() {
|
|
|
3732
3656
|
workspaceState = result.state
|
|
3733
3657
|
if (dryRun || verbose) {
|
|
3734
3658
|
for (const action of result.actions) {
|
|
3735
|
-
printLine(
|
|
3659
|
+
printLine(`${dryRun ? 'Would ' : 'Did '}${action}`)
|
|
3736
3660
|
}
|
|
3737
3661
|
} else if (result.actions.length > 0) {
|
|
3738
|
-
printLine(validateOnly ? '
|
|
3662
|
+
printLine(validateOnly ? 'Workspace validation complete' : 'Workspace setup complete')
|
|
3739
3663
|
} else if (!validateOnly) {
|
|
3740
|
-
printLine('
|
|
3664
|
+
printLine('Workspace setup complete')
|
|
3741
3665
|
}
|
|
3742
3666
|
validationLogPath = writeWorkspaceValidationLog(result.root, result.findings, dryRun)
|
|
3743
3667
|
if (verbose) {
|
|
@@ -3747,40 +3671,32 @@ async function setupCommand() {
|
|
|
3747
3671
|
process.exitCode = 1
|
|
3748
3672
|
}
|
|
3749
3673
|
} else {
|
|
3750
|
-
printLine(
|
|
3674
|
+
printLine('Skipped. Rerun with --workspace to initialize the workspace contract.')
|
|
3751
3675
|
}
|
|
3752
3676
|
}
|
|
3753
3677
|
printLine('')
|
|
3754
|
-
|
|
3755
3678
|
// Step 3: global coding-agent skills
|
|
3756
3679
|
let installOutcomes: SkillInstallOutcome[] = []
|
|
3757
3680
|
printLine('Step 3: Install coding agent skills')
|
|
3758
3681
|
if (hasArg('--no-install') || validateOnly || fix) {
|
|
3759
|
-
printLine(hasArg('--no-install') ? '
|
|
3682
|
+
printLine(hasArg('--no-install') ? 'Skipped (--no-install).' : 'Skipped for validation/repair.')
|
|
3760
3683
|
} else {
|
|
3761
3684
|
let targets = setupSkillTargetsFromArgs()
|
|
3762
3685
|
if (targets.length === 0 && !noInput && !dryRun) {
|
|
3763
|
-
if (await askYesNo('
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
() => true
|
|
3772
|
-
)
|
|
3773
|
-
for (const choice of selectedChoices) {
|
|
3774
|
-
if (!targets.includes(choice.target)) targets.push(choice.target)
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3686
|
+
if (await askYesNo('Install the Orizu skill for your coding agents?', true)) {
|
|
3687
|
+
const selectedChoices = await promptKeyboardMultiSelect(
|
|
3688
|
+
'Choose skill installation destinations',
|
|
3689
|
+
detectedSetupSkillChoices(setupSkillHome),
|
|
3690
|
+
choice => choice.label,
|
|
3691
|
+
() => true
|
|
3692
|
+
)
|
|
3693
|
+
targets = selectedChoices.map(choice => choice.target)
|
|
3777
3694
|
}
|
|
3778
3695
|
}
|
|
3779
|
-
|
|
3780
3696
|
if (targets.length === 0) {
|
|
3781
|
-
printLine('
|
|
3697
|
+
printLine('Skipped skill install.')
|
|
3782
3698
|
} else {
|
|
3783
|
-
printLine('
|
|
3699
|
+
printLine('Installing skills...')
|
|
3784
3700
|
installOutcomes = await applySkillInstallTargets(targets, {
|
|
3785
3701
|
mode: installMode,
|
|
3786
3702
|
skipConfirm: true,
|
|
@@ -3791,22 +3707,21 @@ async function setupCommand() {
|
|
|
3791
3707
|
printResults: false,
|
|
3792
3708
|
})
|
|
3793
3709
|
if (dryRun) {
|
|
3794
|
-
printLine('
|
|
3710
|
+
printLine('Dry run: no skill files were changed.')
|
|
3795
3711
|
}
|
|
3796
3712
|
for (const outcome of installOutcomes) {
|
|
3797
3713
|
const label = setupSkillLabel(outcome.target)
|
|
3798
3714
|
if (outcome.action === 'failed') {
|
|
3799
|
-
printLine(
|
|
3715
|
+
printLine(`${label} failed: ${sanitizeSetupHumanInlineText(outcome.error)}`)
|
|
3800
3716
|
} else if (outcome.action === 'skipped') {
|
|
3801
|
-
printLine(
|
|
3717
|
+
printLine(`${label} skipped`)
|
|
3802
3718
|
} else {
|
|
3803
|
-
printLine(
|
|
3719
|
+
printLine(`${label} installed`)
|
|
3804
3720
|
}
|
|
3805
3721
|
}
|
|
3806
3722
|
}
|
|
3807
3723
|
}
|
|
3808
3724
|
printLine('')
|
|
3809
|
-
|
|
3810
3725
|
// Phase 4: coding-agent handoff
|
|
3811
3726
|
const handoffTeamSlug = setupTeam?.slug || workspaceResult?.teamSlug || null
|
|
3812
3727
|
const handoffProjectSlug = selectedProject?.slug || (workspaceResult?.projectSlugs.length === 1 ? workspaceResult.projectSlugs[0] : null)
|
|
@@ -3820,55 +3735,65 @@ async function setupCommand() {
|
|
|
3820
3735
|
printLine('--- prompt end ---')
|
|
3821
3736
|
printLine('')
|
|
3822
3737
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
.
|
|
3738
|
+
const detected = Object.entries(AGENT_LAUNCH_SPECS)
|
|
3739
|
+
.filter(([, spec]) => findExecutable(spec.command))
|
|
3740
|
+
.map(([agent]) => agent)
|
|
3826
3741
|
let launchAgent = getArg('--launch')
|
|
3827
|
-
|
|
3828
|
-
|
|
3742
|
+
let completedInteractiveHandoff = false
|
|
3743
|
+
if (launchAgent && !(launchAgent in AGENT_LAUNCH_SPECS)) {
|
|
3744
|
+
throw new Error(`Unknown --launch agent '${launchAgent}'. Choices: ${Object.keys(AGENT_LAUNCH_SPECS).join(', ')}.`)
|
|
3829
3745
|
}
|
|
3830
3746
|
|
|
3831
|
-
if (!launchAgent && !noInput && isInteractiveTerminal() &&
|
|
3747
|
+
if (!launchAgent && !noInput && isInteractiveTerminal() && !dryRun) {
|
|
3832
3748
|
const launchChoice = await promptKeyboardSelect(
|
|
3833
3749
|
'Choose an agent to launch',
|
|
3834
|
-
[
|
|
3835
|
-
agent => agent ===
|
|
3750
|
+
[...detected, 'other'],
|
|
3751
|
+
agent => agent === 'other' ? 'Other' : AGENT_LAUNCH_SPECS[agent].displayLabel,
|
|
3752
|
+
{ onEscape: () => 'escape', escapeLabel: 'skip' }
|
|
3836
3753
|
)
|
|
3837
|
-
|
|
3754
|
+
completedInteractiveHandoff = true
|
|
3755
|
+
if (launchChoice === 'other') {
|
|
3756
|
+
if (!handoffRequested) {
|
|
3757
|
+
printLine('--- prompt start ---')
|
|
3758
|
+
printLine(handoffPrompt)
|
|
3759
|
+
printLine('--- prompt end ---')
|
|
3760
|
+
printLine('')
|
|
3761
|
+
}
|
|
3762
|
+
} else if (launchChoice !== 'escape') {
|
|
3763
|
+
launchAgent = launchChoice
|
|
3764
|
+
}
|
|
3838
3765
|
}
|
|
3839
|
-
|
|
3840
3766
|
if (launchAgent) {
|
|
3841
|
-
const
|
|
3767
|
+
const launchSpec = AGENT_LAUNCH_SPECS[launchAgent]
|
|
3768
|
+
const binary = findExecutable(launchSpec.command)
|
|
3842
3769
|
if (inputDisabled) {
|
|
3843
|
-
printLine('
|
|
3770
|
+
printLine('Not launching: setup is non-interactive.')
|
|
3844
3771
|
} else if (!isInteractiveTerminal()) {
|
|
3845
|
-
printLine('
|
|
3772
|
+
printLine('Not launching: --launch requires an interactive terminal.')
|
|
3846
3773
|
} else if (!binary) {
|
|
3847
|
-
printLine(`
|
|
3774
|
+
printLine(`Not launching: '${launchSpec.command}' was not found on PATH.`)
|
|
3848
3775
|
} else if (dryRun) {
|
|
3849
|
-
printLine(`
|
|
3850
|
-
} else if (!getArg('--launch') || skipConfirm || await askYesNo(`
|
|
3851
|
-
printLine(`
|
|
3852
|
-
const launched = spawnSync(binary,
|
|
3776
|
+
printLine(`Would launch ${binary} with the setup prompt.`)
|
|
3777
|
+
} else if (!getArg('--launch') || skipConfirm || await askYesNo(`Launch ${launchSpec.displayLabel} with the setup prompt now?`, true)) {
|
|
3778
|
+
printLine(`Launching ${binary}…`)
|
|
3779
|
+
const launched = spawnSync(binary, launchSpec.buildPromptArgs(handoffPrompt), {
|
|
3780
|
+
cwd: workspaceRoot,
|
|
3781
|
+
stdio: 'inherit',
|
|
3782
|
+
shell: false,
|
|
3783
|
+
})
|
|
3853
3784
|
if (!launched.error) launchedAgent = launchAgent
|
|
3854
3785
|
if (launched.error || (launched.status !== null && launched.status !== 0)) {
|
|
3855
|
-
printLine(
|
|
3786
|
+
printLine(`${launchSpec.displayLabel} exited without completing successfully.`)
|
|
3856
3787
|
}
|
|
3857
3788
|
}
|
|
3858
|
-
} else if (detected.length === 0) {
|
|
3859
|
-
printLine(
|
|
3789
|
+
} else if (detected.length === 0 && !completedInteractiveHandoff) {
|
|
3790
|
+
printLine('No launchable coding agent was found. Read https://orizu.ai/llms.txt to get started.')
|
|
3860
3791
|
}
|
|
3861
3792
|
printLine('')
|
|
3862
3793
|
}
|
|
3863
3794
|
|
|
3864
3795
|
// Setup summary (success criteria checklist)
|
|
3865
|
-
const summaryTargets = installOutcomes.length > 0
|
|
3866
|
-
? installOutcomes
|
|
3867
|
-
.filter(outcome => outcome.action !== 'failed')
|
|
3868
|
-
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target, { cwd: workspaceRoot, homeDir: setupSkillHome }) }))
|
|
3869
|
-
: []
|
|
3870
3796
|
const source = resolveSkillSource()
|
|
3871
|
-
|
|
3872
3797
|
if (hasJsonFlag()) {
|
|
3873
3798
|
printJson({
|
|
3874
3799
|
auth: { state: auth.state, server: auth.baseUrl },
|
|
@@ -3911,21 +3836,10 @@ async function setupCommand() {
|
|
|
3911
3836
|
})
|
|
3912
3837
|
return
|
|
3913
3838
|
}
|
|
3914
|
-
|
|
3915
3839
|
printLine('Setup summary')
|
|
3916
3840
|
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`)
|
|
3917
|
-
|
|
3918
|
-
printLine(
|
|
3919
|
-
for (const { outcome, status } of summaryTargets) {
|
|
3920
|
-
const modeLabel = status.mode ? `, ${status.mode}` : ''
|
|
3921
|
-
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`)
|
|
3922
|
-
}
|
|
3923
|
-
} else {
|
|
3924
|
-
printLine(' Skills: none installed this run')
|
|
3925
|
-
}
|
|
3926
|
-
const failed = installOutcomes.filter(outcome => outcome.action === 'failed')
|
|
3927
|
-
for (const failure of failed) {
|
|
3928
|
-
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`)
|
|
3841
|
+
for (const line of renderSetupSkillSummary(installOutcomes, setupSkillHome)) {
|
|
3842
|
+
printLine(line)
|
|
3929
3843
|
}
|
|
3930
3844
|
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : getWorkspaceRoot(workspaceRoot)}`)
|
|
3931
3845
|
if (workspaceResult) {
|