orizu 0.3.7 → 0.4.1
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 +55 -6
- package/dist/banner.js +20 -0
- package/dist/csv.js +117 -9
- package/dist/help.js +114 -15
- package/dist/index.js +828 -67
- package/dist/setup-prompt.js +22 -0
- package/dist/skill-installer.js +243 -17
- package/dist/task-assignment-manifest.js +59 -0
- package/dist/workspace.js +76 -0
- package/package.json +5 -1
- package/src/banner.ts +21 -0
- package/src/csv.ts +140 -10
- package/src/help.ts +114 -15
- package/src/index.ts +933 -70
- package/src/setup-prompt.ts +27 -0
- package/src/skill-installer.ts +343 -17
- package/src/task-assignment-manifest.ts +96 -0
- package/src/workspace.ts +112 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/__init__.py +2 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +57 -3
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +37 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +233 -17
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +98 -17
- package/vendor/skills/orizu-cli/SKILL.md +13 -6
- package/vendor/skills/orizu-cli/references/cli-reference.md +34 -5
- package/vendor/skills/orizu-cli/references/optimization-reports.md +2 -2
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +1 -0
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +17 -5
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { extractErrorMessage } from './error-response.js'
|
|
|
27
27
|
import { parseGlobalFlags } from './global-flags.js'
|
|
28
28
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js'
|
|
29
29
|
import { runLocalAppPreview } from './preview-runtime.js'
|
|
30
|
+
import { parseAssignmentManifestJsonl } from './task-assignment-manifest.js'
|
|
30
31
|
import {
|
|
31
32
|
assertSecureTokenTransport,
|
|
32
33
|
authedFetch,
|
|
@@ -35,15 +36,32 @@ import {
|
|
|
35
36
|
setGlobalFlags,
|
|
36
37
|
} from './http.js'
|
|
37
38
|
import {
|
|
39
|
+
computeSkillContentHash,
|
|
38
40
|
getSkillInstallPath,
|
|
41
|
+
getSkillTargetStatus,
|
|
42
|
+
getTargetForAgent,
|
|
39
43
|
installSkillTarget,
|
|
44
|
+
isSkillInstallAgent,
|
|
40
45
|
isSkillInstallTarget,
|
|
46
|
+
resolveSkillSource,
|
|
47
|
+
SKILL_INSTALL_AGENTS,
|
|
41
48
|
SKILL_INSTALL_TARGETS,
|
|
49
|
+
SkillInstallMode,
|
|
50
|
+
SkillInstallScope,
|
|
42
51
|
SkillInstallTarget,
|
|
52
|
+
SkillTargetStatus,
|
|
43
53
|
targetNeedsOverwrite,
|
|
44
54
|
} from './skill-installer.js'
|
|
55
|
+
import { renderBanner } from './banner.js'
|
|
45
56
|
import { formatTaskCreateError } from './task-create-error.js'
|
|
57
|
+
import { renderAgentSetupPrompt } from './setup-prompt.js'
|
|
46
58
|
import { LoginResponse } from './types.js'
|
|
59
|
+
import {
|
|
60
|
+
getWorkspaceRoot,
|
|
61
|
+
initOrizuWorkspace,
|
|
62
|
+
WORKSPACE_DIR_NAME,
|
|
63
|
+
workspaceExists,
|
|
64
|
+
} from './workspace.js'
|
|
47
65
|
|
|
48
66
|
interface Team {
|
|
49
67
|
id: string
|
|
@@ -107,6 +125,8 @@ interface PromptSummary {
|
|
|
107
125
|
id: string
|
|
108
126
|
name: string
|
|
109
127
|
role: string
|
|
128
|
+
status?: string
|
|
129
|
+
archivedAt?: string | null
|
|
110
130
|
description?: string | null
|
|
111
131
|
}
|
|
112
132
|
|
|
@@ -279,7 +299,16 @@ function printVersion() {
|
|
|
279
299
|
printLine(`orizu ${getCliVersion()}`)
|
|
280
300
|
}
|
|
281
301
|
|
|
302
|
+
function printBannerIfInteractive() {
|
|
303
|
+
if (!process.stdout.isTTY) {
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
printLine(renderBanner())
|
|
307
|
+
printLine('')
|
|
308
|
+
}
|
|
309
|
+
|
|
282
310
|
function printUsage() {
|
|
311
|
+
printBannerIfInteractive()
|
|
283
312
|
printLine(renderRootHelp())
|
|
284
313
|
}
|
|
285
314
|
|
|
@@ -288,7 +317,7 @@ function printPreviewUsage() {
|
|
|
288
317
|
}
|
|
289
318
|
|
|
290
319
|
function printOptimizationUsage() {
|
|
291
|
-
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] [--num-threads auto|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]`)
|
|
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]`)
|
|
292
321
|
}
|
|
293
322
|
|
|
294
323
|
let cliArgs = process.argv.slice(2)
|
|
@@ -492,15 +521,17 @@ function printPromptSummaries(items: PromptSummary[], emptyMessage: string) {
|
|
|
492
521
|
id: sanitizeTerminalText(item.id),
|
|
493
522
|
name: sanitizeTerminalText(item.name),
|
|
494
523
|
role: sanitizeTerminalText(item.role),
|
|
524
|
+
status: sanitizeTerminalText(item.status || 'active'),
|
|
495
525
|
}))
|
|
496
526
|
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
|
|
497
527
|
const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length))
|
|
498
528
|
const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length))
|
|
529
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length))
|
|
499
530
|
|
|
500
|
-
printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)}`)
|
|
501
|
-
printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)}`)
|
|
531
|
+
printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)} ${'STATUS'.padEnd(statusWidth)}`)
|
|
532
|
+
printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)} ${'-'.repeat(statusWidth)}`)
|
|
502
533
|
rows.forEach(row => {
|
|
503
|
-
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)}`)
|
|
534
|
+
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)} ${row.status.padEnd(statusWidth)}`)
|
|
504
535
|
})
|
|
505
536
|
}
|
|
506
537
|
|
|
@@ -1168,6 +1199,10 @@ async function login() {
|
|
|
1168
1199
|
assertSecureTokenTransport(baseUrl)
|
|
1169
1200
|
|
|
1170
1201
|
if (hasArg('--no-prompt-if-logged-in') && getServerCredentials(baseUrl)) {
|
|
1202
|
+
if (hasJsonFlag()) {
|
|
1203
|
+
printJson({ status: 'already-logged-in', server: baseUrl })
|
|
1204
|
+
return
|
|
1205
|
+
}
|
|
1171
1206
|
printLine(`Already logged in to ${sanitizeTerminalText(baseUrl)}.`)
|
|
1172
1207
|
return
|
|
1173
1208
|
}
|
|
@@ -1262,6 +1297,14 @@ async function login() {
|
|
|
1262
1297
|
apiKey: loginData.apiKey,
|
|
1263
1298
|
})
|
|
1264
1299
|
|
|
1300
|
+
if (hasJsonFlag()) {
|
|
1301
|
+
printJson({
|
|
1302
|
+
status: 'logged-in',
|
|
1303
|
+
server: baseUrl,
|
|
1304
|
+
user: { id: loginData.user.id, email: loginData.user.email ?? null },
|
|
1305
|
+
})
|
|
1306
|
+
return
|
|
1307
|
+
}
|
|
1265
1308
|
printLine(`Logged in as ${sanitizeTerminalText(loginData.user.email ?? loginData.user.id)}`)
|
|
1266
1309
|
}
|
|
1267
1310
|
|
|
@@ -1272,6 +1315,10 @@ async function whoami() {
|
|
|
1272
1315
|
}
|
|
1273
1316
|
|
|
1274
1317
|
const data = await response.json() as { user: { id: string; email: string | null } }
|
|
1318
|
+
if (hasJsonFlag()) {
|
|
1319
|
+
printJson({ user: data.user, server: getBaseUrl() })
|
|
1320
|
+
return
|
|
1321
|
+
}
|
|
1275
1322
|
printLine(sanitizeTerminalText(data.user.email ?? data.user.id))
|
|
1276
1323
|
}
|
|
1277
1324
|
|
|
@@ -1279,6 +1326,10 @@ async function logout() {
|
|
|
1279
1326
|
const baseUrl = getBaseUrl()
|
|
1280
1327
|
const credentials = getServerCredentials(baseUrl)
|
|
1281
1328
|
if (!credentials) {
|
|
1329
|
+
if (hasJsonFlag()) {
|
|
1330
|
+
printJson({ status: 'already-logged-out', server: baseUrl })
|
|
1331
|
+
return
|
|
1332
|
+
}
|
|
1282
1333
|
printLine(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`)
|
|
1283
1334
|
return
|
|
1284
1335
|
}
|
|
@@ -1306,6 +1357,10 @@ async function logout() {
|
|
|
1306
1357
|
}
|
|
1307
1358
|
|
|
1308
1359
|
clearServerCredentials(baseUrl)
|
|
1360
|
+
if (hasJsonFlag()) {
|
|
1361
|
+
printJson({ status: 'logged-out', server: baseUrl, remoteLogoutError })
|
|
1362
|
+
return
|
|
1363
|
+
}
|
|
1309
1364
|
if (remoteLogoutError) {
|
|
1310
1365
|
console.warn(`Warning: remote logout failed: ${remoteLogoutError}`)
|
|
1311
1366
|
}
|
|
@@ -1320,6 +1375,15 @@ async function printEnv() {
|
|
|
1320
1375
|
const projectId = project?.id || getArg('--project-id') || process.env.ORIZU_PROJECT_ID || ''
|
|
1321
1376
|
const projectSlug = project ? `${project.teamSlug}/${project.slug}` : process.env.ORIZU_PROJECT || ''
|
|
1322
1377
|
|
|
1378
|
+
if (hasJsonFlag()) {
|
|
1379
|
+
printJson({
|
|
1380
|
+
ORIZU_API_URL: baseUrl,
|
|
1381
|
+
ORIZU_TOKEN: token,
|
|
1382
|
+
ORIZU_PROJECT_ID: projectId,
|
|
1383
|
+
...(projectSlug ? { ORIZU_PROJECT: projectSlug } : {}),
|
|
1384
|
+
})
|
|
1385
|
+
return
|
|
1386
|
+
}
|
|
1323
1387
|
printLine(`export ORIZU_API_URL=${shellQuote(baseUrl)}`)
|
|
1324
1388
|
printLine(`export ORIZU_TOKEN=${shellQuote(token)}`)
|
|
1325
1389
|
printLine(`export ORIZU_PROJECT_ID=${shellQuote(projectId)}`)
|
|
@@ -1361,28 +1425,52 @@ async function logOptimizationEvent() {
|
|
|
1361
1425
|
}
|
|
1362
1426
|
|
|
1363
1427
|
const data = await parseJsonResponse<{ eventId: string }>(response, 'Optimization event log')
|
|
1428
|
+
if (hasJsonFlag()) {
|
|
1429
|
+
printJson({ eventId: data.eventId })
|
|
1430
|
+
return
|
|
1431
|
+
}
|
|
1364
1432
|
printLine(`Logged event ${sanitizeTerminalText(data.eventId)}`)
|
|
1365
1433
|
}
|
|
1366
1434
|
|
|
1367
1435
|
async function listPrompts() {
|
|
1368
1436
|
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1369
|
-
const
|
|
1437
|
+
const status = getArg('--status') || 'active'
|
|
1438
|
+
if (!['active', 'archived', 'all'].includes(status)) {
|
|
1439
|
+
throw new Error('Usage: orizu prompts list --project <team/project> [--status active|archived|all]')
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
const params = new URLSearchParams({ project, status })
|
|
1443
|
+
const response = await authedFetch(`/api/cli/prompts?${params.toString()}`)
|
|
1370
1444
|
if (!response.ok) {
|
|
1371
1445
|
throw new Error(`Failed to fetch prompts: ${await response.text()}`)
|
|
1372
1446
|
}
|
|
1373
1447
|
|
|
1374
1448
|
const data = await parseJsonResponse<{ prompts: PromptSummary[] }>(response, 'Prompts list')
|
|
1449
|
+
if (hasJsonFlag()) {
|
|
1450
|
+
printJson(data as unknown as Record<string, unknown>)
|
|
1451
|
+
return
|
|
1452
|
+
}
|
|
1375
1453
|
printPromptSummaries(data.prompts, 'No prompts found.')
|
|
1376
1454
|
}
|
|
1377
1455
|
|
|
1378
1456
|
async function listJudges() {
|
|
1379
1457
|
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1380
|
-
const
|
|
1458
|
+
const status = getArg('--status') || 'active'
|
|
1459
|
+
if (!['active', 'archived', 'all'].includes(status)) {
|
|
1460
|
+
throw new Error('Usage: orizu judges list --project <team/project> [--status active|archived|all]')
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const params = new URLSearchParams({ project, status })
|
|
1464
|
+
const response = await authedFetch(`/api/cli/judges?${params.toString()}`)
|
|
1381
1465
|
if (!response.ok) {
|
|
1382
1466
|
throw new Error(`Failed to fetch judges: ${await response.text()}`)
|
|
1383
1467
|
}
|
|
1384
1468
|
|
|
1385
1469
|
const data = await parseJsonResponse<{ judges: PromptSummary[] }>(response, 'Judges list')
|
|
1470
|
+
if (hasJsonFlag()) {
|
|
1471
|
+
printJson(data as unknown as Record<string, unknown>)
|
|
1472
|
+
return
|
|
1473
|
+
}
|
|
1386
1474
|
printPromptSummaries(data.judges, 'No judges found.')
|
|
1387
1475
|
}
|
|
1388
1476
|
|
|
@@ -1425,6 +1513,10 @@ async function listScorers() {
|
|
|
1425
1513
|
}
|
|
1426
1514
|
|
|
1427
1515
|
const data = await parseJsonResponse<{ scorers: ScorerSummary[] }>(response, 'Scorers list')
|
|
1516
|
+
if (hasJsonFlag()) {
|
|
1517
|
+
printJson(data as unknown as Record<string, unknown>)
|
|
1518
|
+
return
|
|
1519
|
+
}
|
|
1428
1520
|
printScorerSummaries(data.scorers)
|
|
1429
1521
|
}
|
|
1430
1522
|
|
|
@@ -1775,6 +1867,40 @@ async function setPromptLabel() {
|
|
|
1775
1867
|
printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`)
|
|
1776
1868
|
}
|
|
1777
1869
|
|
|
1870
|
+
async function setPromptArchivedState(archived: boolean) {
|
|
1871
|
+
const promptRef = getPositionalArg(2)
|
|
1872
|
+
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
1873
|
+
const command = archived ? 'archive' : 'restore'
|
|
1874
|
+
|
|
1875
|
+
if (!promptRef) {
|
|
1876
|
+
throw new Error(`Usage: orizu prompts ${command} <prompt-id-or-name> --project <team/project> [--json]`)
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
const response = await authedFetch(`/api/cli/prompts/${encodeURIComponent(promptRef)}?project=${encodeURIComponent(project)}`, {
|
|
1880
|
+
method: 'PATCH',
|
|
1881
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1882
|
+
body: JSON.stringify({ archived }),
|
|
1883
|
+
})
|
|
1884
|
+
|
|
1885
|
+
if (!response.ok) {
|
|
1886
|
+
throw new Error(`Failed to ${command} prompt: ${await response.text()}`)
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
const data = await parseJsonResponse<{
|
|
1890
|
+
prompt: {
|
|
1891
|
+
id: string
|
|
1892
|
+
name: string
|
|
1893
|
+
status: string
|
|
1894
|
+
}
|
|
1895
|
+
}>(response, `Prompt ${command}`)
|
|
1896
|
+
if (hasJsonFlag()) {
|
|
1897
|
+
printJson(data)
|
|
1898
|
+
return
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
printLine(`${archived ? 'Archived' : 'Restored'} prompt ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)})`)
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1778
1904
|
async function bindPromptScorer(role: 'headline' | 'tracked') {
|
|
1779
1905
|
const promptId = getPositionalArg(3)
|
|
1780
1906
|
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
@@ -2032,6 +2158,10 @@ async function submitRunResults() {
|
|
|
2032
2158
|
perRowResultsStoragePath: string
|
|
2033
2159
|
}
|
|
2034
2160
|
}>(response, 'Run submit')
|
|
2161
|
+
if (hasJsonFlag()) {
|
|
2162
|
+
printJson({ run: data.run })
|
|
2163
|
+
return
|
|
2164
|
+
}
|
|
2035
2165
|
printLine(
|
|
2036
2166
|
`Submitted run ${sanitizeTerminalText(data.run.id)} ` +
|
|
2037
2167
|
`(${formatPercent(data.run.aggregateScore)}) -> ` +
|
|
@@ -2595,10 +2725,19 @@ async function runGepaOptimization() {
|
|
|
2595
2725
|
if (result.status !== 0) {
|
|
2596
2726
|
throw new Error(`orizu-gepa failed with exit code ${result.status}`)
|
|
2597
2727
|
}
|
|
2728
|
+
|
|
2729
|
+
if (hasJsonFlag()) {
|
|
2730
|
+
printJson({ status: 'completed', exitCode: 0 })
|
|
2731
|
+
}
|
|
2598
2732
|
}
|
|
2599
2733
|
|
|
2600
2734
|
async function listTeams() {
|
|
2601
|
-
|
|
2735
|
+
const teams = await fetchTeams()
|
|
2736
|
+
if (hasJsonFlag()) {
|
|
2737
|
+
printJson({ teams })
|
|
2738
|
+
return
|
|
2739
|
+
}
|
|
2740
|
+
printTeams(teams)
|
|
2602
2741
|
}
|
|
2603
2742
|
|
|
2604
2743
|
async function resolveTeamSlug(teamSlugArg: string | null): Promise<string> {
|
|
@@ -2643,13 +2782,22 @@ async function createTeam() {
|
|
|
2643
2782
|
}
|
|
2644
2783
|
|
|
2645
2784
|
const data = await parseJsonResponse<{ team: Team }>(response, 'Team create')
|
|
2785
|
+
if (hasJsonFlag()) {
|
|
2786
|
+
printJson({ team: data.team })
|
|
2787
|
+
return
|
|
2788
|
+
}
|
|
2646
2789
|
printLine(`Created team: ${sanitizeTerminalText(data.team.name)} (${sanitizeTerminalText(data.team.slug)})`)
|
|
2647
2790
|
}
|
|
2648
2791
|
|
|
2649
2792
|
async function listProjects() {
|
|
2650
2793
|
const teamSlugArg = getArg('--team')
|
|
2651
2794
|
const teamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null
|
|
2652
|
-
|
|
2795
|
+
const projects = await fetchProjects(teamSlug || undefined)
|
|
2796
|
+
if (hasJsonFlag()) {
|
|
2797
|
+
printJson({ projects })
|
|
2798
|
+
return
|
|
2799
|
+
}
|
|
2800
|
+
printProjects(projects)
|
|
2653
2801
|
}
|
|
2654
2802
|
|
|
2655
2803
|
async function createProject() {
|
|
@@ -2684,17 +2832,31 @@ async function createProject() {
|
|
|
2684
2832
|
const data = await parseJsonResponse<{
|
|
2685
2833
|
project: { id: string; name: string; slug: string; teamSlug: string }
|
|
2686
2834
|
}>(response, 'Project create')
|
|
2835
|
+
if (hasJsonFlag()) {
|
|
2836
|
+
printJson({ project: data.project })
|
|
2837
|
+
return
|
|
2838
|
+
}
|
|
2687
2839
|
printLine(`Created project ${sanitizeTerminalText(`${data.project.teamSlug}/${data.project.slug}`)}`)
|
|
2688
2840
|
}
|
|
2689
2841
|
|
|
2690
2842
|
async function listTasks() {
|
|
2691
2843
|
const project = getArg('--project')
|
|
2692
|
-
|
|
2844
|
+
const tasks = await fetchTasks(project || undefined)
|
|
2845
|
+
if (hasJsonFlag()) {
|
|
2846
|
+
printJson({ tasks })
|
|
2847
|
+
return
|
|
2848
|
+
}
|
|
2849
|
+
printTasks(tasks)
|
|
2693
2850
|
}
|
|
2694
2851
|
|
|
2695
2852
|
async function listApps() {
|
|
2696
2853
|
const project = getArg('--project') || await resolveProjectSlug(null)
|
|
2697
|
-
|
|
2854
|
+
const apps = await fetchApps(project)
|
|
2855
|
+
if (hasJsonFlag()) {
|
|
2856
|
+
printJson({ apps })
|
|
2857
|
+
return
|
|
2858
|
+
}
|
|
2859
|
+
printApps(apps)
|
|
2698
2860
|
}
|
|
2699
2861
|
|
|
2700
2862
|
function readSourceFile(pathArg: string): string {
|
|
@@ -2759,29 +2921,97 @@ async function askYesNo(question: string, defaultYes: boolean): Promise<boolean>
|
|
|
2759
2921
|
}
|
|
2760
2922
|
}
|
|
2761
2923
|
|
|
2762
|
-
|
|
2924
|
+
const SKILL_INSTALL_USAGE =
|
|
2925
|
+
`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
|
+
|
|
2927
|
+
interface SkillInstallChoice {
|
|
2928
|
+
label: string
|
|
2929
|
+
target: SkillInstallTarget
|
|
2930
|
+
defaultYes: boolean
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
function describeSkillTarget(target: SkillInstallTarget): string {
|
|
2934
|
+
if (target === 'claude-user') return 'Claude Code, for you across all projects'
|
|
2935
|
+
if (target === 'claude-project') return 'Claude Code, only for this project'
|
|
2936
|
+
if (target === 'agent-user') return 'Codex / Open Agent Skills, for you across all projects'
|
|
2937
|
+
if (target === 'agents-project') return 'Codex / Open Agent Skills, only for this project'
|
|
2938
|
+
if (target === 'codex-project') return 'Codex legacy project folder (.codex/skills)'
|
|
2939
|
+
return 'Project instructions, managed Orizu section in AGENTS.md'
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
async function promptSkillInstallTargets(scope: SkillInstallScope): Promise<SkillInstallTarget[]> {
|
|
2763
2943
|
if (!isInteractiveTerminal()) {
|
|
2764
|
-
throw new Error(
|
|
2765
|
-
`Usage: orizu install-skill --target <${SKILL_INSTALL_TARGETS.join('|')}> [--yes] [--dry-run]`
|
|
2766
|
-
)
|
|
2944
|
+
throw new Error(SKILL_INSTALL_USAGE)
|
|
2767
2945
|
}
|
|
2768
2946
|
|
|
2769
|
-
|
|
2947
|
+
const choices: SkillInstallChoice[] = [
|
|
2948
|
+
{ label: describeSkillTarget(getTargetForAgent('claude', scope)), target: getTargetForAgent('claude', scope), defaultYes: true },
|
|
2949
|
+
{ label: describeSkillTarget(getTargetForAgent('codex', scope)), target: getTargetForAgent('codex', scope), defaultYes: true },
|
|
2950
|
+
{ label: describeSkillTarget('agents-md'), target: 'agents-md', defaultYes: false },
|
|
2951
|
+
]
|
|
2952
|
+
|
|
2953
|
+
printLine('Where should Orizu be available?')
|
|
2770
2954
|
const targets: SkillInstallTarget[] = []
|
|
2771
|
-
for (const
|
|
2772
|
-
const
|
|
2773
|
-
if (await askYesNo(`
|
|
2774
|
-
targets.push(target)
|
|
2955
|
+
for (const choice of choices) {
|
|
2956
|
+
const installPath = getSkillInstallPath(choice.target)
|
|
2957
|
+
if (await askYesNo(` ${choice.label}\n ${installPath}?`, choice.defaultYes)) {
|
|
2958
|
+
targets.push(choice.target)
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
if (await askYesNo(' Show advanced targets?', false)) {
|
|
2963
|
+
for (const target of SKILL_INSTALL_TARGETS) {
|
|
2964
|
+
if (targets.includes(target)) {
|
|
2965
|
+
continue
|
|
2966
|
+
}
|
|
2967
|
+
const installPath = getSkillInstallPath(target)
|
|
2968
|
+
if (await askYesNo(` ${describeSkillTarget(target)} [${target}]\n ${installPath}?`, false)) {
|
|
2969
|
+
targets.push(target)
|
|
2970
|
+
}
|
|
2775
2971
|
}
|
|
2776
2972
|
}
|
|
2777
2973
|
|
|
2778
2974
|
return targets
|
|
2779
2975
|
}
|
|
2780
2976
|
|
|
2781
|
-
function
|
|
2782
|
-
const
|
|
2977
|
+
function parseSkillInstallScope(): SkillInstallScope {
|
|
2978
|
+
const raw = getArg('--scope')
|
|
2979
|
+
if (raw === null || raw === 'global') {
|
|
2980
|
+
return 'global'
|
|
2981
|
+
}
|
|
2982
|
+
if (raw === 'project') {
|
|
2983
|
+
return 'local'
|
|
2984
|
+
}
|
|
2985
|
+
throw new Error(`Unknown --scope '${raw}'. Choices: global, project.`)
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
function parseSkillInstallMode(): SkillInstallMode {
|
|
2989
|
+
const raw = getArg('--mode')
|
|
2990
|
+
if (raw === null) {
|
|
2991
|
+
return 'auto'
|
|
2992
|
+
}
|
|
2993
|
+
if (raw !== 'auto' && raw !== 'link' && raw !== 'copy') {
|
|
2994
|
+
throw new Error(`Unknown --mode '${raw}'. Choices: auto, link, copy.`)
|
|
2995
|
+
}
|
|
2996
|
+
return raw
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
function parseSkillInstallTargets(scope: SkillInstallScope): SkillInstallTarget[] {
|
|
2783
3000
|
const targets: SkillInstallTarget[] = []
|
|
2784
|
-
|
|
3001
|
+
|
|
3002
|
+
for (const agent of getArgs('--agent')) {
|
|
3003
|
+
if (!isSkillInstallAgent(agent)) {
|
|
3004
|
+
throw new Error(
|
|
3005
|
+
`Unknown agent '${agent}'. Available agents: ${SKILL_INSTALL_AGENTS.join(', ')}`
|
|
3006
|
+
)
|
|
3007
|
+
}
|
|
3008
|
+
const target = getTargetForAgent(agent, scope)
|
|
3009
|
+
if (!targets.includes(target)) {
|
|
3010
|
+
targets.push(target)
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
for (const target of getArgs('--target')) {
|
|
2785
3015
|
if (!isSkillInstallTarget(target)) {
|
|
2786
3016
|
throw new Error(
|
|
2787
3017
|
`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`
|
|
@@ -2791,6 +3021,7 @@ function parseSkillInstallTargets(): SkillInstallTarget[] {
|
|
|
2791
3021
|
targets.push(target)
|
|
2792
3022
|
}
|
|
2793
3023
|
}
|
|
3024
|
+
|
|
2794
3025
|
return targets
|
|
2795
3026
|
}
|
|
2796
3027
|
|
|
@@ -2801,41 +3032,446 @@ function formatSkillInstallAction(action: string): string {
|
|
|
2801
3032
|
return 'Would update'
|
|
2802
3033
|
}
|
|
2803
3034
|
|
|
2804
|
-
|
|
2805
|
-
const
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3035
|
+
function describePlannedWrite(target: SkillInstallTarget, action: string, mode: string, path: string): string {
|
|
3036
|
+
const verb = action.includes('create') || action === 'created' ? 'create' : 'replace'
|
|
3037
|
+
if (target === 'agents-md') {
|
|
3038
|
+
return ` ${path} (${verb} managed Orizu section)`
|
|
3039
|
+
}
|
|
3040
|
+
if (mode === 'link') {
|
|
3041
|
+
return ` ${path} (${verb}, symlink to the CLI-managed skill)`
|
|
2810
3042
|
}
|
|
3043
|
+
return ` ${path} (${verb}, full copy with sync metadata)`
|
|
3044
|
+
}
|
|
2811
3045
|
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
3046
|
+
interface SkillInstallOutcome {
|
|
3047
|
+
target: SkillInstallTarget
|
|
3048
|
+
path: string
|
|
3049
|
+
action: 'created' | 'updated' | 'skipped' | 'failed'
|
|
3050
|
+
mode: 'link' | 'copy' | 'section' | null
|
|
3051
|
+
error?: string
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
async function applySkillInstallTargets(
|
|
3055
|
+
targets: SkillInstallTarget[],
|
|
3056
|
+
options: { mode: SkillInstallMode, skipConfirm: boolean, dryRun: boolean }
|
|
3057
|
+
): Promise<SkillInstallOutcome[]> {
|
|
3058
|
+
const cliVersion = getCliVersion()
|
|
3059
|
+
const jsonMode = hasJsonFlag()
|
|
3060
|
+
const planned = targets.map(target =>
|
|
3061
|
+
installSkillTarget(target, { dryRun: true, mode: options.mode, cliVersion })
|
|
3062
|
+
)
|
|
3063
|
+
|
|
3064
|
+
if (!jsonMode) {
|
|
3065
|
+
printLine('Planned writes:')
|
|
3066
|
+
for (const plan of planned) {
|
|
3067
|
+
printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path))
|
|
3068
|
+
}
|
|
3069
|
+
printLine('')
|
|
2815
3070
|
}
|
|
2816
3071
|
|
|
3072
|
+
if (options.dryRun) {
|
|
3073
|
+
if (!jsonMode) {
|
|
3074
|
+
printLine('Dry run: no files were changed.')
|
|
3075
|
+
}
|
|
3076
|
+
return planned.map(plan => ({
|
|
3077
|
+
target: plan.target,
|
|
3078
|
+
path: plan.path,
|
|
3079
|
+
action: 'skipped',
|
|
3080
|
+
mode: plan.mode,
|
|
3081
|
+
}))
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
const outcomes: SkillInstallOutcome[] = []
|
|
2817
3085
|
for (const target of targets) {
|
|
2818
|
-
let overwrite = skipConfirm
|
|
3086
|
+
let overwrite = options.skipConfirm
|
|
2819
3087
|
const installPath = getSkillInstallPath(target)
|
|
2820
|
-
if (!
|
|
3088
|
+
if (!overwrite && targetNeedsOverwrite(target)) {
|
|
2821
3089
|
if (!isInteractiveTerminal()) {
|
|
2822
3090
|
throw new Error(`${installPath} already exists. Pass --yes to replace it.`)
|
|
2823
3091
|
}
|
|
2824
3092
|
overwrite = await askYesNo(`Replace ${installPath}?`, true)
|
|
2825
3093
|
if (!overwrite) {
|
|
2826
|
-
|
|
3094
|
+
if (!jsonMode) {
|
|
3095
|
+
printLine(`Skipped ${installPath}`)
|
|
3096
|
+
}
|
|
3097
|
+
outcomes.push({ target, path: installPath, action: 'skipped', mode: null })
|
|
2827
3098
|
continue
|
|
2828
3099
|
}
|
|
2829
3100
|
}
|
|
2830
3101
|
|
|
3102
|
+
try {
|
|
3103
|
+
const result = installSkillTarget(target, {
|
|
3104
|
+
overwrite,
|
|
3105
|
+
mode: options.mode,
|
|
3106
|
+
cliVersion,
|
|
3107
|
+
})
|
|
3108
|
+
if (!jsonMode) {
|
|
3109
|
+
const suffix = result.mode === 'link' ? ' (symlink)' : ''
|
|
3110
|
+
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`)
|
|
3111
|
+
}
|
|
3112
|
+
outcomes.push({
|
|
3113
|
+
target,
|
|
3114
|
+
path: result.path,
|
|
3115
|
+
action: result.action as 'created' | 'updated',
|
|
3116
|
+
mode: result.mode,
|
|
3117
|
+
})
|
|
3118
|
+
} catch (error: any) {
|
|
3119
|
+
const message = error?.message || String(error)
|
|
3120
|
+
if (!jsonMode) {
|
|
3121
|
+
printLine(`Failed ${installPath}: ${message}`)
|
|
3122
|
+
}
|
|
3123
|
+
outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message })
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
return outcomes
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
async function installSkillCommand() {
|
|
3131
|
+
const skipConfirm = hasArg('--yes')
|
|
3132
|
+
const dryRun = hasArg('--dry-run')
|
|
3133
|
+
const scope = parseSkillInstallScope()
|
|
3134
|
+
const mode = parseSkillInstallMode()
|
|
3135
|
+
let targets = parseSkillInstallTargets(scope)
|
|
3136
|
+
if (targets.length === 0) {
|
|
3137
|
+
targets = await promptSkillInstallTargets(scope)
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
if (targets.length === 0) {
|
|
3141
|
+
if (hasJsonFlag()) {
|
|
3142
|
+
printJson({ installs: [] })
|
|
3143
|
+
return
|
|
3144
|
+
}
|
|
3145
|
+
printLine('No targets selected; nothing to do.')
|
|
3146
|
+
return
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
const outcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun })
|
|
3150
|
+
if (hasJsonFlag()) {
|
|
3151
|
+
printJson({ dryRun, installs: outcomes })
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
function describeSkillTargetState(status: SkillTargetStatus): string {
|
|
3156
|
+
if (status.state === 'current') return 'current'
|
|
3157
|
+
if (status.state === 'stale') return 'stale (run `orizu skills update`)'
|
|
3158
|
+
if (status.state === 'broken-link') return 'broken symlink (run `orizu skills update`)'
|
|
3159
|
+
if (status.state === 'unmanaged') return 'exists without a managed Orizu section'
|
|
3160
|
+
return 'not installed'
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
function skillsStatusCommand() {
|
|
3164
|
+
const source = resolveSkillSource()
|
|
3165
|
+
const statuses = SKILL_INSTALL_TARGETS.map(target => getSkillTargetStatus(target))
|
|
3166
|
+
|
|
3167
|
+
if (hasJsonFlag()) {
|
|
3168
|
+
printJson({
|
|
3169
|
+
name: source.name,
|
|
3170
|
+
root: source.root,
|
|
3171
|
+
source: source.source,
|
|
3172
|
+
cliVersion: getCliVersion(),
|
|
3173
|
+
sourceHash: computeSkillContentHash(source.root),
|
|
3174
|
+
targets: statuses.map(status => ({
|
|
3175
|
+
target: status.target,
|
|
3176
|
+
path: status.path,
|
|
3177
|
+
state: status.state,
|
|
3178
|
+
mode: status.mode,
|
|
3179
|
+
linkTarget: status.linkTarget,
|
|
3180
|
+
installedHash: status.installedHash,
|
|
3181
|
+
meta: status.meta,
|
|
3182
|
+
})),
|
|
3183
|
+
})
|
|
3184
|
+
return
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
printLine(`Skill source: ${source.root} (${source.source})`)
|
|
3188
|
+
printLine('')
|
|
3189
|
+
for (const status of statuses) {
|
|
3190
|
+
const mode = status.mode ? `, ${status.mode}` : ''
|
|
3191
|
+
printLine(` ${status.target.padEnd(16)} ${describeSkillTargetState(status)}${mode}`)
|
|
3192
|
+
printLine(` ${''.padEnd(16)} ${status.path}`)
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
async function skillsUpdateCommand() {
|
|
3197
|
+
const dryRun = hasArg('--dry-run')
|
|
3198
|
+
const cliVersion = getCliVersion()
|
|
3199
|
+
const updates: Array<{ target: SkillInstallTarget, path: string, action: string }> = []
|
|
3200
|
+
|
|
3201
|
+
for (const target of SKILL_INSTALL_TARGETS) {
|
|
3202
|
+
const status = getSkillTargetStatus(target)
|
|
3203
|
+
if (status.state === 'missing' || status.state === 'unmanaged') {
|
|
3204
|
+
continue
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
if (status.state === 'current') {
|
|
3208
|
+
updates.push({ target, path: status.path, action: 'already-current' })
|
|
3209
|
+
continue
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
if (dryRun) {
|
|
3213
|
+
updates.push({ target, path: status.path, action: 'would-update' })
|
|
3214
|
+
continue
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
const refreshMode: SkillInstallMode = status.mode === 'link' ? 'auto' : 'copy'
|
|
2831
3218
|
const result = installSkillTarget(target, {
|
|
2832
|
-
overwrite,
|
|
2833
|
-
|
|
3219
|
+
overwrite: true,
|
|
3220
|
+
mode: refreshMode,
|
|
3221
|
+
cliVersion,
|
|
3222
|
+
})
|
|
3223
|
+
updates.push({
|
|
3224
|
+
target,
|
|
3225
|
+
path: status.path,
|
|
3226
|
+
action: status.state === 'broken-link' ? 'relinked' : result.action,
|
|
2834
3227
|
})
|
|
2835
|
-
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
if (hasJsonFlag()) {
|
|
3231
|
+
printJson({ updates })
|
|
3232
|
+
return
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
if (updates.length === 0) {
|
|
3236
|
+
printLine('No Orizu skill installs found. Run `orizu install-skill` first.')
|
|
3237
|
+
return
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
for (const update of updates) {
|
|
3241
|
+
if (update.action === 'already-current') {
|
|
3242
|
+
printLine(`Current ${update.path}`)
|
|
3243
|
+
} else if (update.action === 'would-update') {
|
|
3244
|
+
printLine(`Would update ${update.path}`)
|
|
3245
|
+
} else if (update.action === 'relinked') {
|
|
3246
|
+
printLine(`Relinked ${update.path}`)
|
|
3247
|
+
} else {
|
|
3248
|
+
printLine(`Updated ${update.path}`)
|
|
3249
|
+
}
|
|
2836
3250
|
}
|
|
2837
3251
|
}
|
|
2838
3252
|
|
|
3253
|
+
function skillsPathCommand() {
|
|
3254
|
+
const source = resolveSkillSource()
|
|
3255
|
+
|
|
3256
|
+
if (hasJsonFlag()) {
|
|
3257
|
+
printJson({
|
|
3258
|
+
name: source.name,
|
|
3259
|
+
root: source.root,
|
|
3260
|
+
skillMd: source.skillMd,
|
|
3261
|
+
source: source.source,
|
|
3262
|
+
cliVersion: getCliVersion(),
|
|
3263
|
+
skillHash: computeSkillContentHash(source.root),
|
|
3264
|
+
})
|
|
3265
|
+
return
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
printLine(hasArg('--skill-md') ? source.skillMd : source.root)
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
const LAUNCHABLE_AGENTS: Record<string, string> = {
|
|
3272
|
+
claude: 'claude',
|
|
3273
|
+
codex: 'codex',
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
function findExecutable(name: string): string | null {
|
|
3277
|
+
const pathValue = process.env.PATH || ''
|
|
3278
|
+
for (const dir of pathValue.split(delimiter)) {
|
|
3279
|
+
if (!dir) {
|
|
3280
|
+
continue
|
|
3281
|
+
}
|
|
3282
|
+
const candidate = join(dir, name)
|
|
3283
|
+
if (existsSync(candidate)) {
|
|
3284
|
+
return candidate
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
return null
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
function describeSetupAuthState(): { state: 'signed-in' | 'signed-out', baseUrl: string } {
|
|
3291
|
+
const baseUrl = getBaseUrl()
|
|
3292
|
+
return {
|
|
3293
|
+
state: getServerCredentials(baseUrl) ? 'signed-in' : 'signed-out',
|
|
3294
|
+
baseUrl,
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
async function setupCommand() {
|
|
3299
|
+
const dryRun = hasArg('--dry-run')
|
|
3300
|
+
const skipConfirm = hasArg('--yes')
|
|
3301
|
+
const noInput = hasArg('--no-input') || !isInteractiveTerminal()
|
|
3302
|
+
|
|
3303
|
+
printBannerIfInteractive()
|
|
3304
|
+
printLine('Orizu setup')
|
|
3305
|
+
printLine('')
|
|
3306
|
+
|
|
3307
|
+
// Phase 1: login
|
|
3308
|
+
printLine('1. Authentication')
|
|
3309
|
+
let auth = describeSetupAuthState()
|
|
3310
|
+
if (hasArg('--skip-login')) {
|
|
3311
|
+
printLine(' Skipped (--skip-login).')
|
|
3312
|
+
} else if (auth.state === 'signed-in') {
|
|
3313
|
+
printLine(` Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`)
|
|
3314
|
+
} else if (noInput || dryRun) {
|
|
3315
|
+
printLine(' Not signed in. Run `orizu login` to authenticate.')
|
|
3316
|
+
} else if (await askYesNo(' Sign in to Orizu now?', true)) {
|
|
3317
|
+
await login()
|
|
3318
|
+
auth = describeSetupAuthState()
|
|
3319
|
+
} else {
|
|
3320
|
+
printLine(' Skipped. Run `orizu login` when ready.')
|
|
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).')
|
|
3342
|
+
}
|
|
3343
|
+
printLine('')
|
|
3344
|
+
|
|
3345
|
+
// Phase 3: local workspace
|
|
3346
|
+
printLine('3. Local workspace')
|
|
3347
|
+
let workspaceState: 'created' | 'exists' | 'skipped' | 'would-create' = 'skipped'
|
|
3348
|
+
if (hasArg('--no-workspace')) {
|
|
3349
|
+
printLine(' Skipped (--no-workspace).')
|
|
3350
|
+
} else if (workspaceExists()) {
|
|
3351
|
+
workspaceState = 'exists'
|
|
3352
|
+
printLine(` ${WORKSPACE_DIR_NAME}/ already initialized.`)
|
|
3353
|
+
} else {
|
|
3354
|
+
const wantsWorkspace = hasArg('--workspace') ||
|
|
3355
|
+
(!noInput && await askYesNo(` Create a gitignored ${WORKSPACE_DIR_NAME}/ workspace for local Orizu artifacts?`, true))
|
|
3356
|
+
if (wantsWorkspace) {
|
|
3357
|
+
const result = initOrizuWorkspace({
|
|
3358
|
+
baseUrl: auth.baseUrl,
|
|
3359
|
+
cliVersion: getCliVersion(),
|
|
3360
|
+
dryRun,
|
|
3361
|
+
})
|
|
3362
|
+
workspaceState = result.state === 'would-create' ? 'would-create' : result.state
|
|
3363
|
+
for (const action of result.actions) {
|
|
3364
|
+
printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`)
|
|
3365
|
+
}
|
|
3366
|
+
} else {
|
|
3367
|
+
printLine(` Skipped. Rerun with --workspace to create ${WORKSPACE_DIR_NAME}/.`)
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
printLine('')
|
|
3371
|
+
|
|
3372
|
+
// Phase 4: coding-agent handoff
|
|
3373
|
+
const handoffPrompt = renderAgentSetupPrompt({
|
|
3374
|
+
workspacePath: workspaceState === 'created' || workspaceState === 'exists'
|
|
3375
|
+
? getWorkspaceRoot()
|
|
3376
|
+
: null,
|
|
3377
|
+
})
|
|
3378
|
+
if (!hasArg('--no-handoff')) {
|
|
3379
|
+
printLine('4. Coding-agent handoff')
|
|
3380
|
+
printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:')
|
|
3381
|
+
printLine('')
|
|
3382
|
+
printLine('--- prompt start ---')
|
|
3383
|
+
printLine(handoffPrompt)
|
|
3384
|
+
printLine('--- prompt end ---')
|
|
3385
|
+
printLine('')
|
|
3386
|
+
|
|
3387
|
+
const detected = Object.keys(LAUNCHABLE_AGENTS).filter(agent => findExecutable(LAUNCHABLE_AGENTS[agent]))
|
|
3388
|
+
if (detected.length > 0) {
|
|
3389
|
+
printLine(` Detected agents: ${detected.join(', ')}. Launch one with the prompt, e.g.:`)
|
|
3390
|
+
for (const agent of detected) {
|
|
3391
|
+
printLine(` ${LAUNCHABLE_AGENTS[agent]} "$(orizu setup prompt)"`)
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
const launchAgent = getArg('--launch')
|
|
3396
|
+
if (launchAgent) {
|
|
3397
|
+
if (!(launchAgent in LAUNCHABLE_AGENTS)) {
|
|
3398
|
+
throw new Error(`Unknown --launch agent '${launchAgent}'. Choices: ${Object.keys(LAUNCHABLE_AGENTS).join(', ')}.`)
|
|
3399
|
+
}
|
|
3400
|
+
const binary = findExecutable(LAUNCHABLE_AGENTS[launchAgent])
|
|
3401
|
+
if (!isInteractiveTerminal()) {
|
|
3402
|
+
printLine(' Not launching: --launch requires an interactive terminal.')
|
|
3403
|
+
} else if (!binary) {
|
|
3404
|
+
printLine(` Not launching: '${LAUNCHABLE_AGENTS[launchAgent]}' was not found on PATH.`)
|
|
3405
|
+
} else if (dryRun) {
|
|
3406
|
+
printLine(` Would launch ${binary} with the setup prompt.`)
|
|
3407
|
+
} else if (skipConfirm || await askYesNo(` Launch ${LAUNCHABLE_AGENTS[launchAgent]} with the setup prompt now?`, true)) {
|
|
3408
|
+
printLine(` Launching ${binary}…`)
|
|
3409
|
+
spawnSync(binary, [handoffPrompt], { stdio: 'inherit' })
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
printLine('')
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
// Setup summary (success criteria checklist)
|
|
3416
|
+
const summaryTargets = installOutcomes.length > 0
|
|
3417
|
+
? installOutcomes
|
|
3418
|
+
.filter(outcome => outcome.action !== 'failed')
|
|
3419
|
+
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
|
|
3420
|
+
: []
|
|
3421
|
+
const source = resolveSkillSource()
|
|
3422
|
+
|
|
3423
|
+
if (hasJsonFlag()) {
|
|
3424
|
+
printJson({
|
|
3425
|
+
auth: { state: auth.state, server: auth.baseUrl },
|
|
3426
|
+
integrations: installOutcomes.map(outcome => ({
|
|
3427
|
+
target: outcome.target,
|
|
3428
|
+
path: outcome.path,
|
|
3429
|
+
action: outcome.action,
|
|
3430
|
+
mode: outcome.mode,
|
|
3431
|
+
error: outcome.error,
|
|
3432
|
+
})),
|
|
3433
|
+
skill: {
|
|
3434
|
+
root: source.root,
|
|
3435
|
+
source: source.source,
|
|
3436
|
+
cliVersion: getCliVersion(),
|
|
3437
|
+
skillHash: computeSkillContentHash(source.root),
|
|
3438
|
+
},
|
|
3439
|
+
workspace: { state: workspaceState, path: getWorkspaceRoot() },
|
|
3440
|
+
})
|
|
3441
|
+
return
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
printLine('Setup summary')
|
|
3445
|
+
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`)
|
|
3446
|
+
if (summaryTargets.length > 0) {
|
|
3447
|
+
printLine(' Integrations:')
|
|
3448
|
+
for (const { outcome, status } of summaryTargets) {
|
|
3449
|
+
const modeLabel = status.mode ? `, ${status.mode}` : ''
|
|
3450
|
+
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`)
|
|
3451
|
+
}
|
|
3452
|
+
} else {
|
|
3453
|
+
printLine(' Integrations: none installed this run — `orizu install-skill` sets them up.')
|
|
3454
|
+
}
|
|
3455
|
+
const failed = installOutcomes.filter(outcome => outcome.action === 'failed')
|
|
3456
|
+
for (const failure of failed) {
|
|
3457
|
+
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`)
|
|
3458
|
+
}
|
|
3459
|
+
printLine(` Skill source: ${source.root} (${source.source})`)
|
|
3460
|
+
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : `${getWorkspaceRoot()} (${workspaceState})`}`)
|
|
3461
|
+
printLine(' Next: hand the prompt above to your coding agent to plan repo-specific Orizu adoption.')
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
function setupPromptCommand() {
|
|
3465
|
+
const prompt = renderAgentSetupPrompt({
|
|
3466
|
+
workspacePath: workspaceExists() ? getWorkspaceRoot() : null,
|
|
3467
|
+
})
|
|
3468
|
+
if (hasJsonFlag()) {
|
|
3469
|
+
printJson({ prompt })
|
|
3470
|
+
return
|
|
3471
|
+
}
|
|
3472
|
+
printLine(prompt)
|
|
3473
|
+
}
|
|
3474
|
+
|
|
2839
3475
|
function readJsonFile(pathArg: string): Record<string, unknown> {
|
|
2840
3476
|
const raw = readSourceFile(pathArg)
|
|
2841
3477
|
try {
|
|
@@ -3100,6 +3736,10 @@ async function createAppFromFile() {
|
|
|
3100
3736
|
app: { id: string; name: string; versionNum: number; componentName?: string; url?: string }
|
|
3101
3737
|
warnings?: string[]
|
|
3102
3738
|
}>(response, 'App create')
|
|
3739
|
+
if (hasJsonFlag()) {
|
|
3740
|
+
printJson({ app: data.app, warnings: data.warnings || [] })
|
|
3741
|
+
return
|
|
3742
|
+
}
|
|
3103
3743
|
printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`)
|
|
3104
3744
|
if (data.app.url) {
|
|
3105
3745
|
printLine(`View app: ${formatTerminalLink(data.app.url)}`)
|
|
@@ -3140,6 +3780,14 @@ async function previewAppFromFile() {
|
|
|
3140
3780
|
keepOpen,
|
|
3141
3781
|
})
|
|
3142
3782
|
|
|
3783
|
+
if (hasJsonFlag()) {
|
|
3784
|
+
printJson({
|
|
3785
|
+
url: result.url,
|
|
3786
|
+
screenshotPath: result.screenshotPath || null,
|
|
3787
|
+
warnings: result.warnings,
|
|
3788
|
+
})
|
|
3789
|
+
return
|
|
3790
|
+
}
|
|
3143
3791
|
printLine(`Preview rendered: ${formatTerminalLink(result.url)}`)
|
|
3144
3792
|
if (result.screenshotPath) {
|
|
3145
3793
|
printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`)
|
|
@@ -3191,6 +3839,10 @@ async function updateAppFromFile() {
|
|
|
3191
3839
|
app: { id: string; name: string; versionNum: number; componentName?: string }
|
|
3192
3840
|
warnings?: string[]
|
|
3193
3841
|
}>(response, 'App update')
|
|
3842
|
+
if (hasJsonFlag()) {
|
|
3843
|
+
printJson({ app: data.app, warnings: data.warnings || [] })
|
|
3844
|
+
return
|
|
3845
|
+
}
|
|
3194
3846
|
printLine(`Updated app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) to v${data.app.versionNum}`)
|
|
3195
3847
|
if (data.warnings?.length) {
|
|
3196
3848
|
printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`)
|
|
@@ -3238,6 +3890,10 @@ async function linkAppDataset() {
|
|
|
3238
3890
|
versionNum: number
|
|
3239
3891
|
}>(response, 'App link dataset')
|
|
3240
3892
|
|
|
3893
|
+
if (hasJsonFlag()) {
|
|
3894
|
+
printJson({ app: data.app, linkedDataset: data.linkedDataset, versionNum: data.versionNum })
|
|
3895
|
+
return
|
|
3896
|
+
}
|
|
3241
3897
|
printLine(
|
|
3242
3898
|
`Linked dataset ${sanitizeTerminalText(data.linkedDataset.name)} (${sanitizeTerminalText(data.linkedDataset.id)}) to app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) version ${data.versionNum}`
|
|
3243
3899
|
)
|
|
@@ -3253,48 +3909,82 @@ function parseCommaSeparated(value: string | null): string[] {
|
|
|
3253
3909
|
.filter(Boolean)
|
|
3254
3910
|
}
|
|
3255
3911
|
|
|
3912
|
+
function readAssignmentManifest(path: string | null): ReturnType<typeof parseAssignmentManifestJsonl> | null {
|
|
3913
|
+
if (!path) {
|
|
3914
|
+
return null
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
const content = readFileSync(expandHomePath(path), 'utf8')
|
|
3918
|
+
return parseAssignmentManifestJsonl(content)
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3256
3921
|
async function createTask() {
|
|
3257
3922
|
const projectSlug = getArg('--project')
|
|
3258
3923
|
const datasetId = getArg('--dataset')
|
|
3259
3924
|
const appId = getArg('--app')
|
|
3260
3925
|
const title = getArg('--title')
|
|
3261
3926
|
const assignees = parseCommaSeparated(getArg('--assignees'))
|
|
3927
|
+
const assignmentFile = getArg('--assignment-file')
|
|
3262
3928
|
const publish = hasArg('--publish')
|
|
3263
3929
|
const versionArg = getArg('--version')
|
|
3264
3930
|
const instructions = getArg('--instructions')
|
|
3265
3931
|
const labelsPerItemArg = getArg('--labels-per-item')
|
|
3266
|
-
const
|
|
3932
|
+
const parsedLabelsPerItem = labelsPerItemArg ? Number(labelsPerItemArg) : 1
|
|
3933
|
+
const labelsPerItem =
|
|
3934
|
+
Number.isInteger(parsedLabelsPerItem) && parsedLabelsPerItem > 0
|
|
3935
|
+
? parsedLabelsPerItem
|
|
3936
|
+
: null
|
|
3267
3937
|
const parsedVersionNum = versionArg ? Number(versionArg) : Number.NaN
|
|
3268
3938
|
const versionNum =
|
|
3269
3939
|
Number.isInteger(parsedVersionNum) && parsedVersionNum > 0 ? parsedVersionNum : null
|
|
3270
3940
|
|
|
3271
|
-
if (
|
|
3272
|
-
throw new Error('
|
|
3941
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3942
|
+
throw new Error('Use either --assignment-file or --assignees, not both.')
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile)
|
|
3946
|
+
|
|
3947
|
+
if (!projectSlug || !datasetId || !appId || !title || (publish && assignees.length === 0 && !explicitAssignments)) {
|
|
3948
|
+
throw new Error('Usage: orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--publish] [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]')
|
|
3273
3949
|
}
|
|
3274
3950
|
|
|
3275
3951
|
if (versionArg && versionNum === null) {
|
|
3276
3952
|
throw new Error('--version must be a positive integer')
|
|
3277
3953
|
}
|
|
3278
3954
|
|
|
3955
|
+
if (labelsPerItemArg && labelsPerItem === null) {
|
|
3956
|
+
throw new Error('--labels-per-item must be a positive integer')
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
const requestBody: Record<string, unknown> = {
|
|
3960
|
+
projectSlug,
|
|
3961
|
+
datasetId,
|
|
3962
|
+
appId,
|
|
3963
|
+
versionNum,
|
|
3964
|
+
title,
|
|
3965
|
+
publish,
|
|
3966
|
+
instructions,
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
if (explicitAssignments) {
|
|
3970
|
+
requestBody.explicitAssignments = explicitAssignments
|
|
3971
|
+
} else {
|
|
3972
|
+
requestBody.memberIds = assignees
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (!explicitAssignments || labelsPerItemArg) {
|
|
3976
|
+
requestBody.requiredAssignmentsPerRow = labelsPerItem
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3279
3979
|
const response = await authedFetch('/api/cli/tasks', {
|
|
3280
3980
|
method: 'POST',
|
|
3281
3981
|
headers: { 'Content-Type': 'application/json' },
|
|
3282
|
-
body: JSON.stringify(
|
|
3283
|
-
projectSlug,
|
|
3284
|
-
datasetId,
|
|
3285
|
-
appId,
|
|
3286
|
-
versionNum,
|
|
3287
|
-
title,
|
|
3288
|
-
memberIds: assignees,
|
|
3289
|
-
publish,
|
|
3290
|
-
instructions,
|
|
3291
|
-
requiredAssignmentsPerRow: labelsPerItem,
|
|
3292
|
-
}),
|
|
3982
|
+
body: JSON.stringify(requestBody),
|
|
3293
3983
|
})
|
|
3294
3984
|
|
|
3295
3985
|
if (!response.ok) {
|
|
3296
3986
|
const cliError = await formatTaskCreateError(response)
|
|
3297
|
-
if (
|
|
3987
|
+
if (hasJsonFlag()) {
|
|
3298
3988
|
const payload = cliError.structuredPayload ?? { error: cliError.message }
|
|
3299
3989
|
printLine(JSON.stringify({
|
|
3300
3990
|
...payload,
|
|
@@ -3318,7 +4008,7 @@ async function createTask() {
|
|
|
3318
4008
|
warning?: string
|
|
3319
4009
|
}>(response, 'Task create')
|
|
3320
4010
|
|
|
3321
|
-
if (
|
|
4011
|
+
if (hasJsonFlag()) {
|
|
3322
4012
|
printLine(JSON.stringify({
|
|
3323
4013
|
taskId: data.task.id,
|
|
3324
4014
|
datasetId,
|
|
@@ -3327,13 +4017,16 @@ async function createTask() {
|
|
|
3327
4017
|
taskUrl: `${getBaseUrl()}/d/${projectSlug}/tasks/${data.task.id}`,
|
|
3328
4018
|
title: data.task.title,
|
|
3329
4019
|
status: data.task.status,
|
|
4020
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3330
4021
|
requiredAssignmentsPerRow: data.task.requiredAssignmentsPerRow,
|
|
3331
4022
|
assignmentsCreated: data.assignmentsCreated,
|
|
3332
4023
|
draft: data.task.status === 'draft',
|
|
3333
4024
|
...(data.task.status === 'draft'
|
|
3334
4025
|
? {
|
|
3335
4026
|
message: 'Draft created. Test it manually before assigning.',
|
|
3336
|
-
publishCommand:
|
|
4027
|
+
publishCommand: explicitAssignments
|
|
4028
|
+
? `orizu tasks publish --task ${data.task.id} --assignment-file ${assignmentFile ? shellQuote(assignmentFile) : '<path>'}`
|
|
4029
|
+
: `orizu tasks publish --task ${data.task.id} --assignees <userId1,userId2>`,
|
|
3337
4030
|
}
|
|
3338
4031
|
: {}),
|
|
3339
4032
|
...(data.assignmentShortfall !== undefined ? { assignmentShortfall: data.assignmentShortfall } : {}),
|
|
@@ -3350,12 +4043,17 @@ async function createTask() {
|
|
|
3350
4043
|
`\n Task ID: ${sanitizeTerminalText(data.task.id)}` +
|
|
3351
4044
|
`\n Dataset ID: ${sanitizeTerminalText(datasetId)}` +
|
|
3352
4045
|
`\n Version: v${data.task.versionNum} (${sanitizeTerminalText(data.task.versionId)})` +
|
|
4046
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3353
4047
|
`\n Labels/row: ${data.task.requiredAssignmentsPerRow}` +
|
|
3354
4048
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
3355
4049
|
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '') +
|
|
3356
4050
|
`\n URL: ${sanitizeTerminalText(taskUrl)}` +
|
|
3357
4051
|
(isDraft
|
|
3358
|
-
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval:
|
|
4052
|
+
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval: ${
|
|
4053
|
+
explicitAssignments
|
|
4054
|
+
? `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignment-file ${sanitizeTerminalText(assignmentFile || '<path>')}`
|
|
4055
|
+
: `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`
|
|
4056
|
+
}`
|
|
3359
4057
|
: '')
|
|
3360
4058
|
)
|
|
3361
4059
|
}
|
|
@@ -3363,15 +4061,26 @@ async function createTask() {
|
|
|
3363
4061
|
async function publishTask() {
|
|
3364
4062
|
const taskId = getArg('--task')
|
|
3365
4063
|
const assignees = parseCommaSeparated(getArg('--assignees'))
|
|
4064
|
+
const assignmentFile = getArg('--assignment-file')
|
|
3366
4065
|
|
|
3367
|
-
if (
|
|
3368
|
-
throw new Error('
|
|
4066
|
+
if (assignmentFile && assignees.length > 0) {
|
|
4067
|
+
throw new Error('Use either --assignment-file or --assignees, not both.')
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile)
|
|
4071
|
+
|
|
4072
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
4073
|
+
throw new Error('Usage: orizu tasks publish --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--json]')
|
|
3369
4074
|
}
|
|
3370
4075
|
|
|
3371
4076
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/publish`, {
|
|
3372
4077
|
method: 'POST',
|
|
3373
4078
|
headers: { 'Content-Type': 'application/json' },
|
|
3374
|
-
body: JSON.stringify(
|
|
4079
|
+
body: JSON.stringify(
|
|
4080
|
+
explicitAssignments
|
|
4081
|
+
? { explicitAssignments }
|
|
4082
|
+
: { memberIds: assignees }
|
|
4083
|
+
),
|
|
3375
4084
|
})
|
|
3376
4085
|
|
|
3377
4086
|
if (!response.ok) {
|
|
@@ -3390,10 +4099,11 @@ async function publishTask() {
|
|
|
3390
4099
|
'Task publish'
|
|
3391
4100
|
)
|
|
3392
4101
|
|
|
3393
|
-
if (
|
|
4102
|
+
if (hasJsonFlag()) {
|
|
3394
4103
|
printLine(JSON.stringify({
|
|
3395
4104
|
taskId: data.task.id,
|
|
3396
4105
|
status: data.task.status,
|
|
4106
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3397
4107
|
assignmentsCreated: data.assignmentsCreated,
|
|
3398
4108
|
assignmentsProcessed: data.assignmentsProcessed,
|
|
3399
4109
|
...(data.assignmentsRemoved !== undefined
|
|
@@ -3409,6 +4119,7 @@ async function publishTask() {
|
|
|
3409
4119
|
|
|
3410
4120
|
printLine(
|
|
3411
4121
|
`Published task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]` +
|
|
4122
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3412
4123
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
3413
4124
|
(data.assignmentsRemoved !== undefined
|
|
3414
4125
|
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
@@ -3420,23 +4131,65 @@ async function publishTask() {
|
|
|
3420
4131
|
async function assignTask() {
|
|
3421
4132
|
const taskId = getArg('--task')
|
|
3422
4133
|
const assignees = parseCommaSeparated(getArg('--assignees'))
|
|
4134
|
+
const assignmentFile = getArg('--assignment-file')
|
|
4135
|
+
|
|
4136
|
+
if (assignmentFile && assignees.length > 0) {
|
|
4137
|
+
throw new Error('Use either --assignment-file or --assignees, not both.')
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile)
|
|
4141
|
+
|
|
4142
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
4143
|
+
throw new Error('Usage: orizu tasks assign --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--replace-existing] [--json]')
|
|
4144
|
+
}
|
|
3423
4145
|
|
|
3424
|
-
|
|
3425
|
-
|
|
4146
|
+
const requestBody: Record<string, unknown> = explicitAssignments
|
|
4147
|
+
? { explicitAssignments }
|
|
4148
|
+
: { memberIds: assignees }
|
|
4149
|
+
if (hasArg('--replace-existing')) {
|
|
4150
|
+
requestBody.replaceExisting = true
|
|
3426
4151
|
}
|
|
3427
4152
|
|
|
3428
4153
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/assign`, {
|
|
3429
4154
|
method: 'POST',
|
|
3430
4155
|
headers: { 'Content-Type': 'application/json' },
|
|
3431
|
-
body: JSON.stringify(
|
|
4156
|
+
body: JSON.stringify(requestBody),
|
|
3432
4157
|
})
|
|
3433
4158
|
|
|
3434
4159
|
if (!response.ok) {
|
|
3435
4160
|
throw new Error(`Failed to assign task: ${await response.text()}`)
|
|
3436
4161
|
}
|
|
3437
4162
|
|
|
3438
|
-
const data = await parseJsonResponse<{
|
|
3439
|
-
|
|
4163
|
+
const data = await parseJsonResponse<{
|
|
4164
|
+
assignmentsProcessed?: number
|
|
4165
|
+
assignmentsCreated: number
|
|
4166
|
+
assignmentsRemoved?: number
|
|
4167
|
+
assignmentShortfall?: number
|
|
4168
|
+
warning?: string
|
|
4169
|
+
}>(response, 'Task assign')
|
|
4170
|
+
if (hasJsonFlag()) {
|
|
4171
|
+
printJson({
|
|
4172
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
4173
|
+
assignmentsProcessed: data.assignmentsProcessed ?? data.assignmentsCreated,
|
|
4174
|
+
assignmentsCreated: data.assignmentsCreated,
|
|
4175
|
+
...(data.assignmentsRemoved !== undefined
|
|
4176
|
+
? { assignmentsRemoved: data.assignmentsRemoved }
|
|
4177
|
+
: {}),
|
|
4178
|
+
...(data.assignmentShortfall !== undefined
|
|
4179
|
+
? { assignmentShortfall: data.assignmentShortfall }
|
|
4180
|
+
: {}),
|
|
4181
|
+
...(data.warning ? { warning: data.warning } : {}),
|
|
4182
|
+
})
|
|
4183
|
+
return
|
|
4184
|
+
}
|
|
4185
|
+
printLine(
|
|
4186
|
+
`Created ${data.assignmentsCreated} assignments.` +
|
|
4187
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
4188
|
+
(data.assignmentsRemoved !== undefined
|
|
4189
|
+
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
4190
|
+
: '') +
|
|
4191
|
+
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '')
|
|
4192
|
+
)
|
|
3440
4193
|
}
|
|
3441
4194
|
|
|
3442
4195
|
async function taskStatus() {
|
|
@@ -3448,7 +4201,7 @@ async function taskStatus() {
|
|
|
3448
4201
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`)
|
|
3449
4202
|
if (!response.ok) {
|
|
3450
4203
|
const rawBody = await response.text()
|
|
3451
|
-
if (
|
|
4204
|
+
if (hasJsonFlag()) {
|
|
3452
4205
|
let errorPayload: Record<string, unknown> = { error: rawBody }
|
|
3453
4206
|
try {
|
|
3454
4207
|
const parsed = JSON.parse(rawBody)
|
|
@@ -3475,7 +4228,7 @@ async function taskStatus() {
|
|
|
3475
4228
|
}
|
|
3476
4229
|
|
|
3477
4230
|
const data = await parseJsonResponse<TaskStatusPayload>(response, 'Task status')
|
|
3478
|
-
if (
|
|
4231
|
+
if (hasJsonFlag()) {
|
|
3479
4232
|
printLine(JSON.stringify(data, null, 2))
|
|
3480
4233
|
return
|
|
3481
4234
|
}
|
|
@@ -3502,6 +4255,10 @@ async function updateTaskStatus(targetStatus: 'paused' | 'active') {
|
|
|
3502
4255
|
}
|
|
3503
4256
|
|
|
3504
4257
|
const data = await parseJsonResponse<{ task: { id: string; status: string } }>(response, 'Task status update')
|
|
4258
|
+
if (hasJsonFlag()) {
|
|
4259
|
+
printJson({ task: data.task })
|
|
4260
|
+
return
|
|
4261
|
+
}
|
|
3505
4262
|
const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused'
|
|
3506
4263
|
printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`)
|
|
3507
4264
|
}
|
|
@@ -3626,7 +4383,7 @@ async function appDetail() {
|
|
|
3626
4383
|
throw new Error(`App '${appId}' not found in project '${projectSlug}'`)
|
|
3627
4384
|
}
|
|
3628
4385
|
|
|
3629
|
-
if (
|
|
4386
|
+
if (hasJsonFlag()) {
|
|
3630
4387
|
printLine(JSON.stringify(detail, null, 2))
|
|
3631
4388
|
return
|
|
3632
4389
|
}
|
|
@@ -3687,6 +4444,14 @@ async function exportAppSource() {
|
|
|
3687
4444
|
: defaultAppExportFilename(data.app.name, data.version.versionNum)
|
|
3688
4445
|
|
|
3689
4446
|
writeTextFileEnsuringDir(filename, data.version.code)
|
|
4447
|
+
if (hasJsonFlag()) {
|
|
4448
|
+
printJson({
|
|
4449
|
+
app: { id: data.app.id, name: data.app.name },
|
|
4450
|
+
versionNum: data.version.versionNum,
|
|
4451
|
+
savedTo: filename,
|
|
4452
|
+
})
|
|
4453
|
+
return
|
|
4454
|
+
}
|
|
3690
4455
|
printLine(
|
|
3691
4456
|
`Saved app ${sanitizeTerminalText(data.app.name)} v${data.version.versionNum} source to ${sanitizeTerminalText(filename)}`
|
|
3692
4457
|
)
|
|
@@ -3694,8 +4459,12 @@ async function exportAppSource() {
|
|
|
3694
4459
|
|
|
3695
4460
|
async function listTeamMembers() {
|
|
3696
4461
|
const teamSlug = await resolveTeamSlug(getArg('--team'))
|
|
3697
|
-
|
|
3698
|
-
|
|
4462
|
+
const members = await fetchTeamMembers(teamSlug)
|
|
4463
|
+
if (hasJsonFlag()) {
|
|
4464
|
+
printJson({ team: teamSlug, members })
|
|
4465
|
+
return
|
|
4466
|
+
}
|
|
4467
|
+
printTeamMembers(members)
|
|
3699
4468
|
}
|
|
3700
4469
|
|
|
3701
4470
|
async function addTeamMember() {
|
|
@@ -3714,6 +4483,10 @@ async function addTeamMember() {
|
|
|
3714
4483
|
throw new Error(`Failed to add team member: ${await response.text()}`)
|
|
3715
4484
|
}
|
|
3716
4485
|
const data = await parseJsonResponse<{ member: TeamMember }>(response, 'Team member add')
|
|
4486
|
+
if (hasJsonFlag()) {
|
|
4487
|
+
printJson({ member: data.member })
|
|
4488
|
+
return
|
|
4489
|
+
}
|
|
3717
4490
|
printLine(`Added team member ${sanitizeTerminalText(data.member.email)} (${sanitizeTerminalText(data.member.id)})`)
|
|
3718
4491
|
}
|
|
3719
4492
|
|
|
@@ -3737,6 +4510,10 @@ async function removeTeamMember() {
|
|
|
3737
4510
|
if (!response.ok) {
|
|
3738
4511
|
throw new Error(`Failed to remove team member: ${await response.text()}`)
|
|
3739
4512
|
}
|
|
4513
|
+
if (hasJsonFlag()) {
|
|
4514
|
+
printJson({ removed: { id: member.id, email: member.email } })
|
|
4515
|
+
return
|
|
4516
|
+
}
|
|
3740
4517
|
printLine(`Removed team member ${sanitizeTerminalText(member.email)}`)
|
|
3741
4518
|
}
|
|
3742
4519
|
|
|
@@ -3771,6 +4548,10 @@ async function changeTeamMemberRole() {
|
|
|
3771
4548
|
throw new Error(`Failed to update member role: ${await response.text()}`)
|
|
3772
4549
|
}
|
|
3773
4550
|
|
|
4551
|
+
if (hasJsonFlag()) {
|
|
4552
|
+
printJson({ member: { id: member.id, email: member.email, role } })
|
|
4553
|
+
return
|
|
4554
|
+
}
|
|
3774
4555
|
printLine(`Updated ${sanitizeTerminalText(member.email)} role to ${sanitizeTerminalText(role)}`)
|
|
3775
4556
|
}
|
|
3776
4557
|
|
|
@@ -3873,6 +4654,10 @@ async function uploadJsonlDatasetInChunks(
|
|
|
3873
4654
|
throw new Error('Dataset file contains no rows')
|
|
3874
4655
|
}
|
|
3875
4656
|
|
|
4657
|
+
if (hasJsonFlag()) {
|
|
4658
|
+
printJson({ dataset })
|
|
4659
|
+
return
|
|
4660
|
+
}
|
|
3876
4661
|
printLine(`Uploaded dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) with ${dataset.rowCount} rows.`)
|
|
3877
4662
|
if (dataset.url) {
|
|
3878
4663
|
printLine(`View dataset: ${formatTerminalLink(dataset.url)}`)
|
|
@@ -3902,6 +4687,10 @@ async function uploadDataset() {
|
|
|
3902
4687
|
const { rows, sourceType } = parseDatasetFile(file)
|
|
3903
4688
|
const data = await createDatasetFromRows(project, datasetName, sourceType, rows, readmeMarkdown)
|
|
3904
4689
|
|
|
4690
|
+
if (hasJsonFlag()) {
|
|
4691
|
+
printJson({ dataset: data.dataset })
|
|
4692
|
+
return
|
|
4693
|
+
}
|
|
3905
4694
|
printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`)
|
|
3906
4695
|
if (data.dataset.url) {
|
|
3907
4696
|
printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`)
|
|
@@ -4039,6 +4828,10 @@ async function downloadDataset() {
|
|
|
4039
4828
|
const bytes = new Uint8Array(await response.arrayBuffer())
|
|
4040
4829
|
writeFileSync(filename, bytes)
|
|
4041
4830
|
|
|
4831
|
+
if (hasJsonFlag()) {
|
|
4832
|
+
printJson({ datasetId, format, savedTo: filename })
|
|
4833
|
+
return
|
|
4834
|
+
}
|
|
4042
4835
|
printLine(`Saved dataset ${sanitizeTerminalText(datasetId)} (${format.toUpperCase()}) to ${sanitizeTerminalText(filename)}`)
|
|
4043
4836
|
}
|
|
4044
4837
|
|
|
@@ -4204,6 +4997,10 @@ async function appendDatasetRows() {
|
|
|
4204
4997
|
}
|
|
4205
4998
|
|
|
4206
4999
|
if (lastResult) {
|
|
5000
|
+
if (hasJsonFlag()) {
|
|
5001
|
+
printJson({ dataset: lastResult.dataset, appendedCount: totalAppended })
|
|
5002
|
+
return
|
|
5003
|
+
}
|
|
4207
5004
|
printLine(
|
|
4208
5005
|
`Appended ${totalAppended} rows to dataset ${sanitizeTerminalText(lastResult.dataset.name)} (${sanitizeTerminalText(lastResult.dataset.id)}). New row count: ${lastResult.dataset.rowCount}`
|
|
4209
5006
|
)
|
|
@@ -4265,6 +5062,10 @@ async function editDatasetRows() {
|
|
|
4265
5062
|
updatedCount: number
|
|
4266
5063
|
}>(response, 'Dataset edit rows')
|
|
4267
5064
|
|
|
5065
|
+
if (hasJsonFlag()) {
|
|
5066
|
+
printJson({ dataset: data.dataset, updatedCount: data.updatedCount })
|
|
5067
|
+
return
|
|
5068
|
+
}
|
|
4268
5069
|
printLine(
|
|
4269
5070
|
`Updated ${data.updatedCount} rows in dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Current row count: ${data.dataset.rowCount}`
|
|
4270
5071
|
)
|
|
@@ -4304,6 +5105,10 @@ async function deleteDatasetRows() {
|
|
|
4304
5105
|
deletedCount: number
|
|
4305
5106
|
}>(response, 'Dataset delete rows')
|
|
4306
5107
|
|
|
5108
|
+
if (hasJsonFlag()) {
|
|
5109
|
+
printJson({ dataset: data.dataset, deletedCount: data.deletedCount })
|
|
5110
|
+
return
|
|
5111
|
+
}
|
|
4307
5112
|
printLine(
|
|
4308
5113
|
`Deleted ${data.deletedCount} rows from dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`
|
|
4309
5114
|
)
|
|
@@ -4358,6 +5163,10 @@ async function deleteDataset() {
|
|
|
4358
5163
|
dataset: { id: string }
|
|
4359
5164
|
}>(response, 'Dataset delete')
|
|
4360
5165
|
|
|
5166
|
+
if (hasJsonFlag()) {
|
|
5167
|
+
printJson({ dataset: data.dataset, deleted: true })
|
|
5168
|
+
return
|
|
5169
|
+
}
|
|
4361
5170
|
printLine(`Deleted dataset ${sanitizeTerminalText(data.dataset.id)}.`)
|
|
4362
5171
|
}
|
|
4363
5172
|
|
|
@@ -4394,6 +5203,10 @@ async function lockDataset() {
|
|
|
4394
5203
|
}
|
|
4395
5204
|
}>(response, 'Dataset lock')
|
|
4396
5205
|
|
|
5206
|
+
if (hasJsonFlag()) {
|
|
5207
|
+
printJson({ dataset: data.dataset })
|
|
5208
|
+
return
|
|
5209
|
+
}
|
|
4397
5210
|
printLine(
|
|
4398
5211
|
`Locked dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) at ${sanitizeTerminalText(data.dataset.lockedAt)}. Row count: ${data.dataset.rowCount}`
|
|
4399
5212
|
)
|
|
@@ -4431,6 +5244,10 @@ async function cloneDataset() {
|
|
|
4431
5244
|
}
|
|
4432
5245
|
}>(response, 'Dataset clone')
|
|
4433
5246
|
|
|
5247
|
+
if (hasJsonFlag()) {
|
|
5248
|
+
printJson({ dataset: data.dataset })
|
|
5249
|
+
return
|
|
5250
|
+
}
|
|
4434
5251
|
printLine(
|
|
4435
5252
|
`Cloned dataset ${sanitizeTerminalText(data.dataset.parentDatasetId)} -> ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Row count: ${data.dataset.rowCount}`
|
|
4436
5253
|
)
|
|
@@ -4462,6 +5279,10 @@ async function downloadAnnotations() {
|
|
|
4462
5279
|
const bytes = new Uint8Array(await response.arrayBuffer())
|
|
4463
5280
|
writeFileSync(filename, bytes)
|
|
4464
5281
|
|
|
5282
|
+
if (hasJsonFlag()) {
|
|
5283
|
+
printJson({ taskId, format, savedTo: filename })
|
|
5284
|
+
return
|
|
5285
|
+
}
|
|
4465
5286
|
printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`)
|
|
4466
5287
|
}
|
|
4467
5288
|
|
|
@@ -4706,7 +5527,11 @@ async function runnersExec() {
|
|
|
4706
5527
|
} else {
|
|
4707
5528
|
writeFileSync(outPath, resultJsonl)
|
|
4708
5529
|
}
|
|
4709
|
-
|
|
5530
|
+
if (hasJsonFlag()) {
|
|
5531
|
+
printJson({ rowCount: context.rows.length, out: outPath })
|
|
5532
|
+
} else {
|
|
5533
|
+
printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`)
|
|
5534
|
+
}
|
|
4710
5535
|
} finally {
|
|
4711
5536
|
materializedRunner.cleanup()
|
|
4712
5537
|
}
|
|
@@ -4747,6 +5572,9 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4747
5572
|
|
|
4748
5573
|
const helpTarget = extractHelpTarget(cliArgs)
|
|
4749
5574
|
if (helpTarget !== null) {
|
|
5575
|
+
if (helpTarget.length === 0) {
|
|
5576
|
+
printBannerIfInteractive()
|
|
5577
|
+
}
|
|
4750
5578
|
printLine(renderHelpForArgs(helpTarget))
|
|
4751
5579
|
return
|
|
4752
5580
|
}
|
|
@@ -4769,6 +5597,16 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4769
5597
|
return
|
|
4770
5598
|
}
|
|
4771
5599
|
|
|
5600
|
+
if (command === 'setup' && subcommand === 'prompt') {
|
|
5601
|
+
setupPromptCommand()
|
|
5602
|
+
return
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
if (command === 'setup') {
|
|
5606
|
+
await setupCommand()
|
|
5607
|
+
return
|
|
5608
|
+
}
|
|
5609
|
+
|
|
4772
5610
|
if (command === 'install-skill') {
|
|
4773
5611
|
await installSkillCommand()
|
|
4774
5612
|
return
|
|
@@ -4779,6 +5617,21 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4779
5617
|
return
|
|
4780
5618
|
}
|
|
4781
5619
|
|
|
5620
|
+
if (command === 'skills' && subcommand === 'path') {
|
|
5621
|
+
skillsPathCommand()
|
|
5622
|
+
return
|
|
5623
|
+
}
|
|
5624
|
+
|
|
5625
|
+
if (command === 'skills' && subcommand === 'status') {
|
|
5626
|
+
skillsStatusCommand()
|
|
5627
|
+
return
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5630
|
+
if (command === 'skills' && subcommand === 'update') {
|
|
5631
|
+
await skillsUpdateCommand()
|
|
5632
|
+
return
|
|
5633
|
+
}
|
|
5634
|
+
|
|
4782
5635
|
if (command === 'login') {
|
|
4783
5636
|
await login()
|
|
4784
5637
|
return
|
|
@@ -4862,6 +5715,16 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4862
5715
|
return
|
|
4863
5716
|
}
|
|
4864
5717
|
|
|
5718
|
+
if (command === 'prompts' && subcommand === 'archive') {
|
|
5719
|
+
await setPromptArchivedState(true)
|
|
5720
|
+
return
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5723
|
+
if (command === 'prompts' && subcommand === 'restore') {
|
|
5724
|
+
await setPromptArchivedState(false)
|
|
5725
|
+
return
|
|
5726
|
+
}
|
|
5727
|
+
|
|
4865
5728
|
if (command === 'prompts' && subcommand === 'labels' && cliArgs[2] === 'set') {
|
|
4866
5729
|
await setPromptLabel()
|
|
4867
5730
|
return
|