orizu 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/artifact-archive.js +152 -0
- package/dist/connectors-cli.js +80 -0
- package/dist/credentials.js +4 -1
- package/dist/daytona-slice-rehearsal.js +255 -0
- package/dist/daytona-slice.js +441 -0
- package/dist/egress-allowlist-cli.js +132 -0
- package/dist/egress-policy.js +159 -0
- package/dist/git-credential.js +131 -0
- package/dist/github-setup.js +226 -0
- package/dist/help.js +276 -21
- package/dist/hosted-bootstrap-rehearsal.js +235 -0
- package/dist/hosted-bootstrap.js +365 -0
- package/dist/hosted-broker-server.js +220 -0
- package/dist/hosted-git-http-server.js +136 -0
- package/dist/hosted-harness-claude.js +493 -0
- package/dist/hosted-harness-opencode.js +729 -0
- package/dist/hosted-harness.js +60 -0
- package/dist/hosted-loop.js +477 -0
- package/dist/hosted-run-event-sink.js +499 -0
- package/dist/hosted-runtime-assets.js +216 -0
- package/dist/hosted-session-cli.js +642 -0
- package/dist/index.js +945 -258
- package/dist/kill-switch-cli.js +89 -0
- package/dist/manifests-cli.js +143 -0
- package/dist/preview-runtime.js +4 -1
- package/dist/sandbox-provider.js +238 -0
- package/dist/secret-redaction.js +112 -0
- package/dist/session-timeline.js +103 -0
- package/dist/setup-prompt.js +4 -2
- package/dist/skill-installer.js +9 -1
- package/dist/smoke.js +250 -0
- package/dist/task-assignment-manifest.js +96 -0
- package/dist/vercel-sandbox-provider.js +260 -0
- package/dist/workbench-cli.js +493 -0
- package/dist/workspace-sync.js +924 -0
- package/dist/workspace.js +868 -57
- package/package.json +4 -1
- package/src/artifact-archive.ts +182 -0
- package/src/connectors-cli.ts +115 -0
- package/src/credentials.ts +6 -2
- package/src/daytona-slice-rehearsal.ts +320 -0
- package/src/daytona-slice.ts +598 -0
- package/src/egress-allowlist-cli.ts +147 -0
- package/src/egress-policy.ts +188 -0
- package/src/git-credential.ts +155 -0
- package/src/github-setup.ts +366 -0
- package/src/help.ts +277 -21
- package/src/hosted-bootstrap-rehearsal.ts +312 -0
- package/src/hosted-bootstrap.ts +571 -0
- package/src/hosted-broker-server.ts +278 -0
- package/src/hosted-git-http-server.ts +164 -0
- package/src/hosted-harness-claude.ts +621 -0
- package/src/hosted-harness-opencode.ts +823 -0
- package/src/hosted-harness.ts +124 -0
- package/src/hosted-loop.ts +680 -0
- package/src/hosted-run-event-sink.ts +632 -0
- package/src/hosted-runtime-assets.ts +272 -0
- package/src/hosted-session-cli.ts +844 -0
- package/src/index.ts +1102 -317
- package/src/kill-switch-cli.ts +113 -0
- package/src/manifests-cli.ts +168 -0
- package/src/preview-runtime.ts +14 -9
- package/src/sandbox-provider.ts +440 -0
- package/src/secret-redaction.ts +124 -0
- package/src/session-timeline.ts +152 -0
- package/src/setup-prompt.ts +4 -2
- package/src/skill-installer.ts +13 -1
- package/src/smoke.ts +342 -0
- package/src/task-assignment-manifest.ts +164 -0
- package/src/vercel-sandbox-provider.ts +388 -0
- package/src/workbench-cli.ts +701 -0
- package/src/workspace-sync.ts +1148 -0
- package/src/workspace.ts +1051 -64
- package/vendor/skills/orizu-cli/SKILL.md +8 -4
- package/vendor/skills/orizu-cli/references/cli-reference.md +12 -4
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
3
|
-
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize } from 'path';
|
|
3
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { createServer } from 'http';
|
|
6
6
|
import { gunzipSync, gzipSync } from 'zlib';
|
|
7
|
-
import { existsSync, mkdirSync, mkdtempSync,
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from 'fs';
|
|
8
8
|
import { spawn, spawnSync } from 'child_process';
|
|
9
|
-
import { tmpdir } from 'os';
|
|
9
|
+
import { homedir, tmpdir } from 'os';
|
|
10
10
|
import { createInterface } from 'readline/promises';
|
|
11
|
+
import { emitKeypressEvents } from 'readline';
|
|
11
12
|
import { stdin as input, stdout as output } from 'process';
|
|
12
13
|
import { clearServerCredentials, getServerCredentials, saveServerCredentials } from './credentials.js';
|
|
13
14
|
import { parseDatasetFile } from './file-parser.js';
|
|
@@ -16,14 +17,45 @@ import { parseDatasetReference } from './dataset-download.js';
|
|
|
16
17
|
import { extractErrorMessage } from './error-response.js';
|
|
17
18
|
import { parseGlobalFlags } from './global-flags.js';
|
|
18
19
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js';
|
|
20
|
+
import { zipDirectoryToBase64 } from './artifact-archive.js';
|
|
19
21
|
import { runLocalAppPreview } from './preview-runtime.js';
|
|
20
|
-
import {
|
|
22
|
+
import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js';
|
|
23
|
+
import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags } from './http.js';
|
|
21
24
|
import { computeSkillContentHash, getSkillInstallPath, getSkillTargetStatus, getTargetForAgent, installSkillTarget, isSkillInstallAgent, isSkillInstallTarget, resolveSkillSource, SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
|
|
22
25
|
import { renderBanner } from './banner.js';
|
|
23
26
|
import { formatTaskCreateError } from './task-create-error.js';
|
|
24
27
|
import { renderAgentSetupPrompt } from './setup-prompt.js';
|
|
25
|
-
import { getWorkspaceRoot, initOrizuWorkspace,
|
|
28
|
+
import { getWorkspaceRoot, initOrizuWorkspace, workspaceExists, } from './workspace.js';
|
|
29
|
+
import { connectorsCommand } from './connectors-cli.js';
|
|
30
|
+
import { killSwitchCommand } from './kill-switch-cli.js';
|
|
31
|
+
import { egressAllowlistCommand } from './egress-allowlist-cli.js';
|
|
32
|
+
import { manifestsCommand } from './manifests-cli.js';
|
|
33
|
+
import { workbenchCommand } from './workbench-cli.js';
|
|
34
|
+
import { hostedCommand } from './hosted-session-cli.js';
|
|
35
|
+
import { workspaceSyncCommand } from './workspace-sync.js';
|
|
36
|
+
import { runGitCredential } from './git-credential.js';
|
|
37
|
+
import { runGithubLink, runInteractiveHostedSetup } from './github-setup.js';
|
|
38
|
+
function getErrorMessage(error) {
|
|
39
|
+
return error instanceof Error ? error.message : String(error);
|
|
40
|
+
}
|
|
41
|
+
function isNodeError(error) {
|
|
42
|
+
return error instanceof Error;
|
|
43
|
+
}
|
|
26
44
|
const MAX_README_LENGTH = 200_000;
|
|
45
|
+
const TASK_STATUS_ACTION_LABELS = {
|
|
46
|
+
paused: {
|
|
47
|
+
verb: 'pause',
|
|
48
|
+
pastTense: 'Paused',
|
|
49
|
+
},
|
|
50
|
+
active: {
|
|
51
|
+
verb: 'unpause',
|
|
52
|
+
pastTense: 'Unpaused',
|
|
53
|
+
},
|
|
54
|
+
completed: {
|
|
55
|
+
verb: 'complete',
|
|
56
|
+
pastTense: 'Completed',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
27
59
|
function getCliVersion() {
|
|
28
60
|
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
29
61
|
if (typeof packageJson.version !== 'string' || packageJson.version.length === 0) {
|
|
@@ -48,12 +80,6 @@ function printUsage() {
|
|
|
48
80
|
printBannerIfInteractive();
|
|
49
81
|
printLine(renderRootHelp());
|
|
50
82
|
}
|
|
51
|
-
function printPreviewUsage() {
|
|
52
|
-
printLine(`\nLocal app preview:\n\n orizu apps preview --file <path> --input-schema <json-path> --output-schema <json-path> --sample-row <json-path> [--screenshot <png-path>] [--headed] [--keep-open] [--component <name>]`);
|
|
53
|
-
}
|
|
54
|
-
function printOptimizationUsage() {
|
|
55
|
-
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]`);
|
|
56
|
-
}
|
|
57
83
|
let cliArgs = process.argv.slice(2);
|
|
58
84
|
let cliJsonOutput = false;
|
|
59
85
|
function getArg(name) {
|
|
@@ -78,6 +104,17 @@ function getArgs(name) {
|
|
|
78
104
|
}
|
|
79
105
|
return values;
|
|
80
106
|
}
|
|
107
|
+
function getOptionalArgValue(name) {
|
|
108
|
+
const index = cliArgs.indexOf(name);
|
|
109
|
+
if (index === -1 || index + 1 >= cliArgs.length) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const value = cliArgs[index + 1];
|
|
113
|
+
if (!value || value.startsWith('-')) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
81
118
|
function rejectDashPrefixedOptionValue(name, value) {
|
|
82
119
|
if (value && value.startsWith('-')) {
|
|
83
120
|
throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
|
|
@@ -122,6 +159,9 @@ export function createCodeChallenge(verifier) {
|
|
|
122
159
|
export function sanitizeTerminalText(value) {
|
|
123
160
|
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
124
161
|
}
|
|
162
|
+
function printError(message) {
|
|
163
|
+
console.error(sanitizeTerminalText(message));
|
|
164
|
+
}
|
|
125
165
|
export function validateBrowserUrl(url, expectedOrigin) {
|
|
126
166
|
let parsed;
|
|
127
167
|
try {
|
|
@@ -265,7 +305,7 @@ function readJsonObjectArg(valueArg, label) {
|
|
|
265
305
|
return parsed;
|
|
266
306
|
}
|
|
267
307
|
catch (error) {
|
|
268
|
-
throw new Error(`Invalid ${label} '${valueArg}': ${
|
|
308
|
+
throw new Error(`Invalid ${label} '${valueArg}': ${getErrorMessage(error)}`);
|
|
269
309
|
}
|
|
270
310
|
}
|
|
271
311
|
function readJsonPayloadArg(pathArg) {
|
|
@@ -351,6 +391,38 @@ async function fetchTeamMembers(teamSlug) {
|
|
|
351
391
|
const data = await parseJsonResponse(response, 'Team members list');
|
|
352
392
|
return data.members;
|
|
353
393
|
}
|
|
394
|
+
async function createTeamOnServer(name) {
|
|
395
|
+
const response = await authedFetch('/api/cli/teams', {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: { 'Content-Type': 'application/json' },
|
|
398
|
+
body: JSON.stringify({ name }),
|
|
399
|
+
});
|
|
400
|
+
if (!response.ok) {
|
|
401
|
+
throw new Error(`Failed to create team: ${await response.text()}`);
|
|
402
|
+
}
|
|
403
|
+
const data = await parseJsonResponse(response, 'Team create');
|
|
404
|
+
return data.team;
|
|
405
|
+
}
|
|
406
|
+
async function createProjectOnServer(teamSlug, name) {
|
|
407
|
+
const response = await authedFetch('/api/cli/projects', {
|
|
408
|
+
method: 'POST',
|
|
409
|
+
headers: { 'Content-Type': 'application/json' },
|
|
410
|
+
body: JSON.stringify({ teamSlug, name }),
|
|
411
|
+
});
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
throw new Error(`Failed to create project: ${await response.text()}`);
|
|
414
|
+
}
|
|
415
|
+
const data = await parseJsonResponse(response, 'Project create');
|
|
416
|
+
return {
|
|
417
|
+
id: data.project.id,
|
|
418
|
+
name: data.project.name,
|
|
419
|
+
slug: data.project.slug,
|
|
420
|
+
teamId: data.project.teamId || '',
|
|
421
|
+
teamName: data.project.teamName || data.project.teamSlug,
|
|
422
|
+
teamSlug: data.project.teamSlug,
|
|
423
|
+
role: data.project.role || 'admin',
|
|
424
|
+
};
|
|
425
|
+
}
|
|
354
426
|
async function resolveProjectSelection(projectArg) {
|
|
355
427
|
projectArg = projectArg || process.env.ORIZU_PROJECT || null;
|
|
356
428
|
const teams = await fetchTeams();
|
|
@@ -370,7 +442,7 @@ async function resolveProjectSelection(projectArg) {
|
|
|
370
442
|
const [teamSlug, projectSlug] = segments.map(normalizeSlugInput);
|
|
371
443
|
const matchedTeam = teams.find(team => team.slug === teamSlug);
|
|
372
444
|
if (!matchedTeam) {
|
|
373
|
-
|
|
445
|
+
printError(`Team '${teamSlug}' not found in your accessible teams.`);
|
|
374
446
|
const selectedTeam = await promptSelect('Select a team', teams, team => `${team.name} (${team.slug})`);
|
|
375
447
|
const projects = await fetchProjects(selectedTeam.slug);
|
|
376
448
|
const selectedProject = await promptSelect(`Select a project in ${selectedTeam.slug}`, projects, project => `${project.name} (${project.teamSlug}/${project.slug})`);
|
|
@@ -379,7 +451,7 @@ async function resolveProjectSelection(projectArg) {
|
|
|
379
451
|
const projects = await fetchProjects(matchedTeam.slug);
|
|
380
452
|
const matchedProject = projects.find(project => project.slug === projectSlug);
|
|
381
453
|
if (!matchedProject) {
|
|
382
|
-
|
|
454
|
+
printError(`Project '${projectSlug}' not found in team '${matchedTeam.slug}'.`);
|
|
383
455
|
const selectedProject = await promptSelect(`Select a project in ${matchedTeam.slug}`, projects, project => `${project.name} (${project.teamSlug}/${project.slug})`);
|
|
384
456
|
return selectedProject;
|
|
385
457
|
}
|
|
@@ -1988,20 +2060,12 @@ async function createTeam() {
|
|
|
1988
2060
|
if (!name) {
|
|
1989
2061
|
throw new Error('Usage: orizu teams create --name <name>');
|
|
1990
2062
|
}
|
|
1991
|
-
const
|
|
1992
|
-
method: 'POST',
|
|
1993
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1994
|
-
body: JSON.stringify({ name }),
|
|
1995
|
-
});
|
|
1996
|
-
if (!response.ok) {
|
|
1997
|
-
throw new Error(`Failed to create team: ${await response.text()}`);
|
|
1998
|
-
}
|
|
1999
|
-
const data = await parseJsonResponse(response, 'Team create');
|
|
2063
|
+
const team = await createTeamOnServer(name);
|
|
2000
2064
|
if (hasJsonFlag()) {
|
|
2001
|
-
printJson({ team
|
|
2065
|
+
printJson({ team });
|
|
2002
2066
|
return;
|
|
2003
2067
|
}
|
|
2004
|
-
printLine(`Created team: ${sanitizeTerminalText(
|
|
2068
|
+
printLine(`Created team: ${sanitizeTerminalText(team.name)} (${sanitizeTerminalText(team.slug)})`);
|
|
2005
2069
|
}
|
|
2006
2070
|
async function listProjects() {
|
|
2007
2071
|
const teamSlugArg = getArg('--team');
|
|
@@ -2024,20 +2088,12 @@ async function createProject() {
|
|
|
2024
2088
|
const team = await promptSelect('Select a team', await fetchTeams(), item => `${item.name} (${item.slug})`, { forcePrompt: true });
|
|
2025
2089
|
teamSlug = team.slug;
|
|
2026
2090
|
}
|
|
2027
|
-
const
|
|
2028
|
-
method: 'POST',
|
|
2029
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2030
|
-
body: JSON.stringify({ teamSlug, name }),
|
|
2031
|
-
});
|
|
2032
|
-
if (!response.ok) {
|
|
2033
|
-
throw new Error(`Failed to create project: ${await response.text()}`);
|
|
2034
|
-
}
|
|
2035
|
-
const data = await parseJsonResponse(response, 'Project create');
|
|
2091
|
+
const project = await createProjectOnServer(teamSlug, name);
|
|
2036
2092
|
if (hasJsonFlag()) {
|
|
2037
|
-
printJson({ project
|
|
2093
|
+
printJson({ project });
|
|
2038
2094
|
return;
|
|
2039
2095
|
}
|
|
2040
|
-
printLine(`Created project ${sanitizeTerminalText(`${
|
|
2096
|
+
printLine(`Created project ${sanitizeTerminalText(`${project.teamSlug}/${project.slug}`)}`);
|
|
2041
2097
|
}
|
|
2042
2098
|
async function listTasks() {
|
|
2043
2099
|
const project = getArg('--project');
|
|
@@ -2063,10 +2119,10 @@ function readSourceFile(pathArg) {
|
|
|
2063
2119
|
return readFileSync(expandedPath, 'utf-8');
|
|
2064
2120
|
}
|
|
2065
2121
|
catch (error) {
|
|
2066
|
-
if (error
|
|
2122
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2067
2123
|
throw new Error(`File not found: ${expandedPath}`);
|
|
2068
2124
|
}
|
|
2069
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2125
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`);
|
|
2070
2126
|
}
|
|
2071
2127
|
}
|
|
2072
2128
|
function readSourceBytes(pathArg) {
|
|
@@ -2075,10 +2131,10 @@ function readSourceBytes(pathArg) {
|
|
|
2075
2131
|
return readFileSync(expandedPath);
|
|
2076
2132
|
}
|
|
2077
2133
|
catch (error) {
|
|
2078
|
-
if (error
|
|
2134
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2079
2135
|
throw new Error(`File not found: ${expandedPath}`);
|
|
2080
2136
|
}
|
|
2081
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2137
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`);
|
|
2082
2138
|
}
|
|
2083
2139
|
}
|
|
2084
2140
|
function hasJsonFlag() {
|
|
@@ -2115,12 +2171,261 @@ async function askYesNo(question, defaultYes) {
|
|
|
2115
2171
|
rl.close();
|
|
2116
2172
|
}
|
|
2117
2173
|
}
|
|
2174
|
+
async function askText(question, defaultValue) {
|
|
2175
|
+
const rl = createInterface({ input, output });
|
|
2176
|
+
try {
|
|
2177
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
2178
|
+
const answer = (await rl.question(`${question}${suffix} `)).trim();
|
|
2179
|
+
return answer || defaultValue;
|
|
2180
|
+
}
|
|
2181
|
+
finally {
|
|
2182
|
+
rl.close();
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
async function waitForAnyKeyOrEscape(message) {
|
|
2186
|
+
if (!isInteractiveTerminal()) {
|
|
2187
|
+
throw new Error('Interactive terminal required.');
|
|
2188
|
+
}
|
|
2189
|
+
printLine(message);
|
|
2190
|
+
const rawInput = input;
|
|
2191
|
+
const wasRaw = Boolean(rawInput.isRaw);
|
|
2192
|
+
emitKeypressEvents(input);
|
|
2193
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
2194
|
+
function cleanup() {
|
|
2195
|
+
input.off('keypress', onKeypress);
|
|
2196
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
2197
|
+
rawInput.setRawMode(false);
|
|
2198
|
+
}
|
|
2199
|
+
input.pause();
|
|
2200
|
+
printLine('');
|
|
2201
|
+
}
|
|
2202
|
+
function onKeypress(_chunk, key) {
|
|
2203
|
+
cleanup();
|
|
2204
|
+
if (key.ctrl && key.name === 'c') {
|
|
2205
|
+
rejectPromise(new Error('Setup cancelled.'));
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
resolvePromise(key.name !== 'escape');
|
|
2209
|
+
}
|
|
2210
|
+
input.on('keypress', onKeypress);
|
|
2211
|
+
input.resume();
|
|
2212
|
+
rawInput.setRawMode?.(true);
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
async function promptKeyboardSelect(title, items, label) {
|
|
2216
|
+
if (items.length === 0) {
|
|
2217
|
+
throw new Error(`No options available for ${title.toLowerCase()}`);
|
|
2218
|
+
}
|
|
2219
|
+
if (!isInteractiveTerminal()) {
|
|
2220
|
+
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`);
|
|
2221
|
+
}
|
|
2222
|
+
printLine(`\n${sanitizeTerminalText(title)}`);
|
|
2223
|
+
printLine('Use ↑/↓ to choose, Enter to confirm, or Esc to cancel.');
|
|
2224
|
+
let selected = 0;
|
|
2225
|
+
let rendered = false;
|
|
2226
|
+
const rawInput = input;
|
|
2227
|
+
const wasRaw = Boolean(rawInput.isRaw);
|
|
2228
|
+
emitKeypressEvents(input);
|
|
2229
|
+
function render() {
|
|
2230
|
+
if (rendered) {
|
|
2231
|
+
output.write(`\x1b[${items.length}A`);
|
|
2232
|
+
}
|
|
2233
|
+
items.forEach((item, index) => {
|
|
2234
|
+
output.write('\x1b[2K');
|
|
2235
|
+
const marker = index === selected ? '●' : '○';
|
|
2236
|
+
output.write(` ${marker} ${sanitizeTerminalText(label(item, index))}\n`);
|
|
2237
|
+
});
|
|
2238
|
+
rendered = true;
|
|
2239
|
+
}
|
|
2240
|
+
render();
|
|
2241
|
+
output.write('\x1b[?25l');
|
|
2242
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
2243
|
+
function cleanup() {
|
|
2244
|
+
input.off('keypress', onKeypress);
|
|
2245
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
2246
|
+
rawInput.setRawMode(false);
|
|
2247
|
+
}
|
|
2248
|
+
input.pause();
|
|
2249
|
+
output.write('\x1b[?25h');
|
|
2250
|
+
printLine('');
|
|
2251
|
+
}
|
|
2252
|
+
function onKeypress(_chunk, key) {
|
|
2253
|
+
if (key.ctrl && key.name === 'c') {
|
|
2254
|
+
cleanup();
|
|
2255
|
+
rejectPromise(new Error('Setup cancelled.'));
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (key.name === 'escape') {
|
|
2259
|
+
cleanup();
|
|
2260
|
+
rejectPromise(new Error('Setup cancelled.'));
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
if (key.name === 'up') {
|
|
2264
|
+
selected = (selected - 1 + items.length) % items.length;
|
|
2265
|
+
render();
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
if (key.name === 'down') {
|
|
2269
|
+
selected = (selected + 1) % items.length;
|
|
2270
|
+
render();
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
2274
|
+
const item = items[selected];
|
|
2275
|
+
cleanup();
|
|
2276
|
+
resolvePromise(item);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
input.on('keypress', onKeypress);
|
|
2280
|
+
input.resume();
|
|
2281
|
+
rawInput.setRawMode?.(true);
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
async function promptKeyboardMultiSelect(title, items, label, defaultSelected) {
|
|
2285
|
+
if (items.length === 0) {
|
|
2286
|
+
return [];
|
|
2287
|
+
}
|
|
2288
|
+
if (!isInteractiveTerminal()) {
|
|
2289
|
+
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`);
|
|
2290
|
+
}
|
|
2291
|
+
printLine(`\n${sanitizeTerminalText(title)}`);
|
|
2292
|
+
printLine('Use ↑/↓ to move, Space to toggle, Enter to confirm, or Esc to skip.');
|
|
2293
|
+
let selected = 0;
|
|
2294
|
+
let rendered = false;
|
|
2295
|
+
const checked = new Set();
|
|
2296
|
+
items.forEach((item, index) => {
|
|
2297
|
+
if (defaultSelected(item, index)) {
|
|
2298
|
+
checked.add(index);
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
const rawInput = input;
|
|
2302
|
+
const wasRaw = Boolean(rawInput.isRaw);
|
|
2303
|
+
emitKeypressEvents(input);
|
|
2304
|
+
function render() {
|
|
2305
|
+
if (rendered) {
|
|
2306
|
+
output.write(`\x1b[${items.length}A`);
|
|
2307
|
+
}
|
|
2308
|
+
items.forEach((item, index) => {
|
|
2309
|
+
output.write('\x1b[2K');
|
|
2310
|
+
const cursor = index === selected ? '›' : ' ';
|
|
2311
|
+
const marker = checked.has(index) ? '◉' : '○';
|
|
2312
|
+
output.write(`${cursor} ${marker} ${sanitizeTerminalText(label(item, index))}\n`);
|
|
2313
|
+
});
|
|
2314
|
+
rendered = true;
|
|
2315
|
+
}
|
|
2316
|
+
render();
|
|
2317
|
+
output.write('\x1b[?25l');
|
|
2318
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
2319
|
+
function cleanup() {
|
|
2320
|
+
input.off('keypress', onKeypress);
|
|
2321
|
+
if (rawInput.setRawMode && !wasRaw) {
|
|
2322
|
+
rawInput.setRawMode(false);
|
|
2323
|
+
}
|
|
2324
|
+
input.pause();
|
|
2325
|
+
output.write('\x1b[?25h');
|
|
2326
|
+
printLine('');
|
|
2327
|
+
}
|
|
2328
|
+
function onKeypress(_chunk, key) {
|
|
2329
|
+
if (key.ctrl && key.name === 'c') {
|
|
2330
|
+
cleanup();
|
|
2331
|
+
rejectPromise(new Error('Setup cancelled.'));
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
if (key.name === 'escape') {
|
|
2335
|
+
cleanup();
|
|
2336
|
+
resolvePromise([]);
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
if (key.name === 'up') {
|
|
2340
|
+
selected = (selected - 1 + items.length) % items.length;
|
|
2341
|
+
render();
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (key.name === 'down') {
|
|
2345
|
+
selected = (selected + 1) % items.length;
|
|
2346
|
+
render();
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (key.name === 'space') {
|
|
2350
|
+
if (checked.has(selected)) {
|
|
2351
|
+
checked.delete(selected);
|
|
2352
|
+
}
|
|
2353
|
+
else {
|
|
2354
|
+
checked.add(selected);
|
|
2355
|
+
}
|
|
2356
|
+
render();
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
2360
|
+
const selectedItems = items.filter((_item, index) => checked.has(index));
|
|
2361
|
+
cleanup();
|
|
2362
|
+
resolvePromise(selectedItems);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
input.on('keypress', onKeypress);
|
|
2366
|
+
input.resume();
|
|
2367
|
+
rawInput.setRawMode?.(true);
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
function formatFindingCount(count, label) {
|
|
2371
|
+
return `${count} ${label}${count === 1 ? '' : 's'}`;
|
|
2372
|
+
}
|
|
2373
|
+
function validationSummary(findings, logPath) {
|
|
2374
|
+
if (findings.length === 0) {
|
|
2375
|
+
return 'passed';
|
|
2376
|
+
}
|
|
2377
|
+
const errors = findings.filter(finding => finding.severity === 'error').length;
|
|
2378
|
+
const warnings = findings.filter(finding => finding.severity === 'warning').length;
|
|
2379
|
+
const infos = findings.filter(finding => finding.severity === 'info').length;
|
|
2380
|
+
const parts = [
|
|
2381
|
+
errors > 0 ? formatFindingCount(errors, 'error') : null,
|
|
2382
|
+
warnings > 0 ? formatFindingCount(warnings, 'warning') : null,
|
|
2383
|
+
infos > 0 ? formatFindingCount(infos, 'info') : null,
|
|
2384
|
+
].filter(Boolean);
|
|
2385
|
+
const location = logPath ? ` (printed to ${logPath})` : '';
|
|
2386
|
+
return `${parts.join(', ')}${location}`;
|
|
2387
|
+
}
|
|
2388
|
+
function writeWorkspaceValidationLog(workspaceRoot, findings, dryRun) {
|
|
2389
|
+
if (findings.length === 0 || dryRun) {
|
|
2390
|
+
return null;
|
|
2391
|
+
}
|
|
2392
|
+
const logsDir = join(workspaceRoot, '.logs');
|
|
2393
|
+
mkdirSync(logsDir, { recursive: true });
|
|
2394
|
+
const logPath = join(logsDir, `${randomBytes(6).toString('hex')}.log`);
|
|
2395
|
+
const lines = [
|
|
2396
|
+
'Orizu workspace validation',
|
|
2397
|
+
`Generated at: ${new Date().toISOString()}`,
|
|
2398
|
+
`Workspace: ${workspaceRoot}`,
|
|
2399
|
+
`Summary: ${validationSummary(findings, null)}`,
|
|
2400
|
+
'',
|
|
2401
|
+
...findings.map(finding => {
|
|
2402
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2403
|
+
return `${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`;
|
|
2404
|
+
}),
|
|
2405
|
+
'',
|
|
2406
|
+
];
|
|
2407
|
+
writeFileSync(logPath, lines.join('\n'), 'utf8');
|
|
2408
|
+
return relative(workspaceRoot, logPath);
|
|
2409
|
+
}
|
|
2410
|
+
function printWorkspaceFindings(findings) {
|
|
2411
|
+
if (findings.length === 0) {
|
|
2412
|
+
printLine(' Validation passed.');
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
printLine(' Validation findings:');
|
|
2416
|
+
for (const finding of findings) {
|
|
2417
|
+
const path = finding.path ? ` ${finding.path}` : '';
|
|
2418
|
+
printLine(` - ${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2118
2421
|
const SKILL_INSTALL_USAGE = `Usage: orizu install-skill [--agent <${SKILL_INSTALL_AGENTS.join('|')}>]... [--scope global|project] [--mode auto|link|copy] [--target <${SKILL_INSTALL_TARGETS.join('|')}>]... [--yes] [--dry-run]`;
|
|
2119
2422
|
function describeSkillTarget(target) {
|
|
2120
2423
|
if (target === 'claude-user')
|
|
2121
2424
|
return 'Claude Code, for you across all projects';
|
|
2122
2425
|
if (target === 'claude-project')
|
|
2123
2426
|
return 'Claude Code, only for this project';
|
|
2427
|
+
if (target === 'codex-user')
|
|
2428
|
+
return 'Codex, for you across all projects';
|
|
2124
2429
|
if (target === 'agent-user')
|
|
2125
2430
|
return 'Codex / Open Agent Skills, for you across all projects';
|
|
2126
2431
|
if (target === 'agents-project')
|
|
@@ -2222,8 +2527,16 @@ function describePlannedWrite(target, action, mode, path) {
|
|
|
2222
2527
|
async function applySkillInstallTargets(targets, options) {
|
|
2223
2528
|
const cliVersion = getCliVersion();
|
|
2224
2529
|
const jsonMode = hasJsonFlag();
|
|
2225
|
-
const planned = targets.map(target => installSkillTarget(target, {
|
|
2226
|
-
|
|
2530
|
+
const planned = targets.map(target => installSkillTarget(target, {
|
|
2531
|
+
cwd: options.cwd,
|
|
2532
|
+
homeDir: options.homeDir,
|
|
2533
|
+
dryRun: true,
|
|
2534
|
+
mode: options.mode,
|
|
2535
|
+
cliVersion,
|
|
2536
|
+
}));
|
|
2537
|
+
const showPlan = options.showPlan ?? true;
|
|
2538
|
+
const printResults = options.printResults ?? true;
|
|
2539
|
+
if (showPlan && !jsonMode) {
|
|
2227
2540
|
printLine('Planned writes:');
|
|
2228
2541
|
for (const plan of planned) {
|
|
2229
2542
|
printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path));
|
|
@@ -2231,7 +2544,7 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2231
2544
|
printLine('');
|
|
2232
2545
|
}
|
|
2233
2546
|
if (options.dryRun) {
|
|
2234
|
-
if (!jsonMode) {
|
|
2547
|
+
if (printResults && !jsonMode) {
|
|
2235
2548
|
printLine('Dry run: no files were changed.');
|
|
2236
2549
|
}
|
|
2237
2550
|
return planned.map(plan => ({
|
|
@@ -2244,8 +2557,8 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2244
2557
|
const outcomes = [];
|
|
2245
2558
|
for (const target of targets) {
|
|
2246
2559
|
let overwrite = options.skipConfirm;
|
|
2247
|
-
const installPath = getSkillInstallPath(target);
|
|
2248
|
-
if (!overwrite && targetNeedsOverwrite(target)) {
|
|
2560
|
+
const installPath = getSkillInstallPath(target, { cwd: options.cwd, homeDir: options.homeDir });
|
|
2561
|
+
if (!overwrite && targetNeedsOverwrite(target, { cwd: options.cwd, homeDir: options.homeDir })) {
|
|
2249
2562
|
if (!isInteractiveTerminal()) {
|
|
2250
2563
|
throw new Error(`${installPath} already exists. Pass --yes to replace it.`);
|
|
2251
2564
|
}
|
|
@@ -2260,11 +2573,13 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2260
2573
|
}
|
|
2261
2574
|
try {
|
|
2262
2575
|
const result = installSkillTarget(target, {
|
|
2576
|
+
cwd: options.cwd,
|
|
2577
|
+
homeDir: options.homeDir,
|
|
2263
2578
|
overwrite,
|
|
2264
2579
|
mode: options.mode,
|
|
2265
2580
|
cliVersion,
|
|
2266
2581
|
});
|
|
2267
|
-
if (!jsonMode) {
|
|
2582
|
+
if (printResults && !jsonMode) {
|
|
2268
2583
|
const suffix = result.mode === 'link' ? ' (symlink)' : '';
|
|
2269
2584
|
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`);
|
|
2270
2585
|
}
|
|
@@ -2276,8 +2591,8 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2276
2591
|
});
|
|
2277
2592
|
}
|
|
2278
2593
|
catch (error) {
|
|
2279
|
-
const message =
|
|
2280
|
-
if (!jsonMode) {
|
|
2594
|
+
const message = getErrorMessage(error);
|
|
2595
|
+
if (printResults && !jsonMode) {
|
|
2281
2596
|
printLine(`Failed ${installPath}: ${message}`);
|
|
2282
2597
|
}
|
|
2283
2598
|
outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message });
|
|
@@ -2439,15 +2754,124 @@ function describeSetupAuthState() {
|
|
|
2439
2754
|
baseUrl,
|
|
2440
2755
|
};
|
|
2441
2756
|
}
|
|
2757
|
+
async function resolveSetupTeam(teamSlugArg, noInput, dryRun) {
|
|
2758
|
+
const teams = await fetchTeams();
|
|
2759
|
+
const normalizedTeamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null;
|
|
2760
|
+
if (normalizedTeamSlug) {
|
|
2761
|
+
const team = teams.find(candidate => candidate.slug === normalizedTeamSlug);
|
|
2762
|
+
if (!team) {
|
|
2763
|
+
throw new Error(`Team '${normalizedTeamSlug}' was not found in your accessible teams.`);
|
|
2764
|
+
}
|
|
2765
|
+
return team;
|
|
2766
|
+
}
|
|
2767
|
+
if (noInput || dryRun) {
|
|
2768
|
+
throw new Error('Authenticated setup requires --team <slug> when running non-interactively.');
|
|
2769
|
+
}
|
|
2770
|
+
const choices = [
|
|
2771
|
+
...teams.map(team => ({ kind: 'team', team })),
|
|
2772
|
+
{ kind: 'create' },
|
|
2773
|
+
];
|
|
2774
|
+
const choice = await promptKeyboardSelect('Choose the team to set up in this directory', choices, item => item.kind === 'create'
|
|
2775
|
+
? 'Create a new team'
|
|
2776
|
+
: `${item.team.name} (${item.team.slug})`);
|
|
2777
|
+
if (choice.kind === 'team') {
|
|
2778
|
+
return choice.team;
|
|
2779
|
+
}
|
|
2780
|
+
const name = await askText(' Team name?', '');
|
|
2781
|
+
if (!name) {
|
|
2782
|
+
throw new Error('Team name is required to create a team.');
|
|
2783
|
+
}
|
|
2784
|
+
return createTeamOnServer(name);
|
|
2785
|
+
}
|
|
2786
|
+
async function resolveSetupProjects(team) {
|
|
2787
|
+
const projects = await fetchProjects(team.slug);
|
|
2788
|
+
if (projects.length === 0) {
|
|
2789
|
+
throw new Error(`Team '${team.slug}' has no projects. Create a project before running setup.`);
|
|
2790
|
+
}
|
|
2791
|
+
return projects;
|
|
2792
|
+
}
|
|
2793
|
+
function projectSeedsFromProjects(projects) {
|
|
2794
|
+
return projects.map(project => ({
|
|
2795
|
+
slug: project.slug,
|
|
2796
|
+
id: project.id,
|
|
2797
|
+
name: project.name,
|
|
2798
|
+
}));
|
|
2799
|
+
}
|
|
2800
|
+
function formatProjectSetupProgress(count) {
|
|
2801
|
+
return `${count} project${count === 1 ? '' : 's'} being set up in workspace...`;
|
|
2802
|
+
}
|
|
2803
|
+
function setupTargetForAgent(agent) {
|
|
2804
|
+
return agent === 'codex' ? 'codex-user' : 'claude-user';
|
|
2805
|
+
}
|
|
2806
|
+
function resolveSetupSkillHome() {
|
|
2807
|
+
return resolve(process.env.HOME || homedir());
|
|
2808
|
+
}
|
|
2809
|
+
function detectedSetupSkillChoices(homeDir) {
|
|
2810
|
+
const choices = [];
|
|
2811
|
+
if (existsSync(join(homeDir, '.codex'))) {
|
|
2812
|
+
choices.push({
|
|
2813
|
+
agent: 'codex',
|
|
2814
|
+
label: 'Codex',
|
|
2815
|
+
target: 'codex-user',
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
if (existsSync(join(homeDir, '.claude'))) {
|
|
2819
|
+
choices.push({
|
|
2820
|
+
agent: 'claude',
|
|
2821
|
+
label: 'Claude Code',
|
|
2822
|
+
target: 'claude-user',
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
return choices;
|
|
2826
|
+
}
|
|
2827
|
+
function setupSkillTargetsFromArgs() {
|
|
2828
|
+
const targets = [];
|
|
2829
|
+
for (const agent of getArgs('--agent')) {
|
|
2830
|
+
if (!isSkillInstallAgent(agent)) {
|
|
2831
|
+
throw new Error(`Unknown agent '${agent}'. Available agents: ${SKILL_INSTALL_AGENTS.join(', ')}`);
|
|
2832
|
+
}
|
|
2833
|
+
const target = setupTargetForAgent(agent);
|
|
2834
|
+
if (!targets.includes(target)) {
|
|
2835
|
+
targets.push(target);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
for (const target of getArgs('--target')) {
|
|
2839
|
+
if (!isSkillInstallTarget(target)) {
|
|
2840
|
+
throw new Error(`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`);
|
|
2841
|
+
}
|
|
2842
|
+
if (!targets.includes(target)) {
|
|
2843
|
+
targets.push(target);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
return targets;
|
|
2847
|
+
}
|
|
2848
|
+
function setupSkillLabel(target) {
|
|
2849
|
+
if (target === 'codex-user' || target === 'codex-project' || target === 'agent-user' || target === 'agents-project') {
|
|
2850
|
+
return 'Codex skill';
|
|
2851
|
+
}
|
|
2852
|
+
if (target === 'claude-user' || target === 'claude-project')
|
|
2853
|
+
return 'Claude Code skill';
|
|
2854
|
+
return 'Orizu skill';
|
|
2855
|
+
}
|
|
2442
2856
|
async function setupCommand() {
|
|
2443
2857
|
const dryRun = hasArg('--dry-run');
|
|
2858
|
+
const validateOnly = hasArg('--validate') && !hasArg('--fix');
|
|
2859
|
+
const fix = hasArg('--fix');
|
|
2860
|
+
const verbose = hasArg('--verbose');
|
|
2861
|
+
const handoffRequested = hasArg('--handoff') || Boolean(getArg('--launch'));
|
|
2444
2862
|
const skipConfirm = hasArg('--yes');
|
|
2445
|
-
const noInput = hasArg('--no-input') || !isInteractiveTerminal();
|
|
2863
|
+
const noInput = hasArg('--no-input') || hasArg('--non-interactive') || !isInteractiveTerminal();
|
|
2864
|
+
const workspaceArg = getOptionalArgValue('--workspace');
|
|
2865
|
+
const workspaceRoot = workspaceArg ? resolve(expandHomePath(workspaceArg)) : process.cwd();
|
|
2866
|
+
const teamArg = getOptionalArgValue('--team');
|
|
2867
|
+
const projectArg = getOptionalArgValue('--project');
|
|
2868
|
+
const setupSkillHome = resolveSetupSkillHome();
|
|
2869
|
+
const installMode = getArg('--mode') ? parseSkillInstallMode() : 'link';
|
|
2446
2870
|
printBannerIfInteractive();
|
|
2447
2871
|
printLine('Orizu setup');
|
|
2448
2872
|
printLine('');
|
|
2449
|
-
//
|
|
2450
|
-
printLine('1
|
|
2873
|
+
// Step 1: login
|
|
2874
|
+
printLine('Step 1: Login');
|
|
2451
2875
|
let auth = describeSetupAuthState();
|
|
2452
2876
|
if (hasArg('--skip-login')) {
|
|
2453
2877
|
printLine(' Skipped (--skip-login).');
|
|
@@ -2458,72 +2882,177 @@ async function setupCommand() {
|
|
|
2458
2882
|
else if (noInput || dryRun) {
|
|
2459
2883
|
printLine(' Not signed in. Run `orizu login` to authenticate.');
|
|
2460
2884
|
}
|
|
2461
|
-
else if (await
|
|
2885
|
+
else if (await waitForAnyKeyOrEscape(' Press any key to open a browser and connect to your account, or Esc to cancel.')) {
|
|
2462
2886
|
await login();
|
|
2463
2887
|
auth = describeSetupAuthState();
|
|
2464
2888
|
}
|
|
2465
2889
|
else {
|
|
2466
|
-
|
|
2890
|
+
throw new Error('Setup cancelled.');
|
|
2467
2891
|
}
|
|
2468
2892
|
printLine('');
|
|
2469
|
-
//
|
|
2470
|
-
printLine('2
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
let
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2893
|
+
// Step 2: local workspace
|
|
2894
|
+
printLine('Step 2: Setup your workspace');
|
|
2895
|
+
let workspaceState = 'skipped';
|
|
2896
|
+
let workspaceResult = null;
|
|
2897
|
+
let validationLogPath = null;
|
|
2898
|
+
let hostedAttached = false;
|
|
2899
|
+
if (hasArg('--no-workspace') && !validateOnly && !fix) {
|
|
2900
|
+
printLine(' Skipped (--no-workspace).');
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
const wantsWorkspace = validateOnly
|
|
2904
|
+
|| fix
|
|
2905
|
+
|| hasArg('--workspace')
|
|
2906
|
+
|| Boolean(teamArg || projectArg)
|
|
2907
|
+
|| workspaceExists(workspaceRoot)
|
|
2908
|
+
|| !noInput;
|
|
2909
|
+
if (wantsWorkspace) {
|
|
2910
|
+
let teamSlug = teamArg;
|
|
2911
|
+
const projectSlug = projectArg;
|
|
2912
|
+
let teamId = null;
|
|
2913
|
+
let projects;
|
|
2914
|
+
if (auth.state === 'signed-in' && !validateOnly) {
|
|
2915
|
+
const team = await resolveSetupTeam(teamSlug, noInput, dryRun);
|
|
2916
|
+
const serverProjects = await resolveSetupProjects(team);
|
|
2917
|
+
teamSlug = team.slug;
|
|
2918
|
+
teamId = team.id;
|
|
2919
|
+
projects = projectSeedsFromProjects(serverProjects);
|
|
2920
|
+
printLine(` ${formatProjectSetupProgress(projects.length)}`);
|
|
2921
|
+
if (!hasArg('--local') && !noInput && !dryRun) {
|
|
2922
|
+
let link = null;
|
|
2923
|
+
try {
|
|
2924
|
+
link = await runGithubLink(team.slug, { print: printLine, fetcher: authedFetch, openUrl: openInBrowser, confirm: (org) => askYesNo(` Connect this team to GitHub org '${org}'?`, true) });
|
|
2925
|
+
}
|
|
2926
|
+
catch (error) {
|
|
2927
|
+
printLine(` GitHub link skipped: ${getErrorMessage(error)} Run \`orizu github link --team ${team.slug}\` to finish.`);
|
|
2928
|
+
}
|
|
2929
|
+
if (link) {
|
|
2930
|
+
try {
|
|
2931
|
+
hostedAttached = (await runInteractiveHostedSetup({ teamSlug: team.slug, targetDir: workspaceRoot, local: hasArg('--local'), activeInstallation: link.status === 'active' }, { print: printLine, fetcher: authedFetch })).attached;
|
|
2932
|
+
}
|
|
2933
|
+
catch (error) {
|
|
2934
|
+
throw new Error(`Hosted setup failed: ${getErrorMessage(error)} Remove any partial clone in ${workspaceRoot} if one exists, then re-run \`orizu setup\` (or pass --local to scaffold a local-only workspace).`);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
else if (!teamSlug && !noInput && !validateOnly) {
|
|
2940
|
+
teamSlug = await askText(' Team slug?', 'local-team');
|
|
2941
|
+
}
|
|
2942
|
+
if (auth.state !== 'signed-in' && (!teamSlug || !projectSlug) && noInput && hasArg('--workspace')) {
|
|
2943
|
+
printLine(' Using starter local-only team/project. Pass --team with authentication to materialize server projects.');
|
|
2944
|
+
}
|
|
2945
|
+
if (projectSlug && auth.state !== 'signed-in') {
|
|
2946
|
+
projects = [{ slug: projectSlug }];
|
|
2947
|
+
}
|
|
2948
|
+
if (auth.state !== 'signed-in' && !validateOnly) {
|
|
2949
|
+
const localProjectCount = projects?.length || 1;
|
|
2950
|
+
printLine(` ${formatProjectSetupProgress(localProjectCount)}`);
|
|
2951
|
+
}
|
|
2952
|
+
if (hostedAttached) {
|
|
2953
|
+
workspaceState = 'exists';
|
|
2954
|
+
}
|
|
2955
|
+
else {
|
|
2956
|
+
const result = initOrizuWorkspace({
|
|
2957
|
+
workspaceRoot,
|
|
2958
|
+
teamSlug,
|
|
2959
|
+
teamId,
|
|
2960
|
+
projectSlug: auth.state === 'signed-in' ? null : projectSlug,
|
|
2961
|
+
projects,
|
|
2962
|
+
baseUrl: auth.baseUrl,
|
|
2963
|
+
serviceOrigin: getOptionalArgValue('--service-origin') || auth.baseUrl,
|
|
2964
|
+
attachWorkspaceId: getOptionalArgValue('--attach-workspace'),
|
|
2965
|
+
cliVersion: getCliVersion(),
|
|
2966
|
+
dryRun,
|
|
2967
|
+
validateOnly,
|
|
2968
|
+
fix,
|
|
2969
|
+
noSymlinks: hasArg('--no-symlinks'),
|
|
2970
|
+
});
|
|
2971
|
+
workspaceResult = result;
|
|
2972
|
+
workspaceState = result.state;
|
|
2973
|
+
if (dryRun || verbose) {
|
|
2974
|
+
for (const action of result.actions) {
|
|
2975
|
+
printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
else if (result.actions.length > 0) {
|
|
2979
|
+
printLine(validateOnly ? ' Workspace validation complete' : ' Workspace setup complete');
|
|
2980
|
+
}
|
|
2981
|
+
else if (!validateOnly) {
|
|
2982
|
+
printLine(' Workspace setup complete');
|
|
2983
|
+
}
|
|
2984
|
+
validationLogPath = writeWorkspaceValidationLog(result.root, result.findings, dryRun);
|
|
2985
|
+
if (verbose) {
|
|
2986
|
+
printWorkspaceFindings(result.findings);
|
|
2987
|
+
}
|
|
2988
|
+
if ((validateOnly || fix) && result.findings.some(finding => finding.severity === 'error')) {
|
|
2989
|
+
process.exitCode = 1;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2477
2992
|
}
|
|
2478
2993
|
else {
|
|
2479
|
-
|
|
2994
|
+
printLine(` Skipped. Rerun with --workspace to initialize the workspace contract.`);
|
|
2480
2995
|
}
|
|
2481
2996
|
}
|
|
2482
|
-
let installOutcomes = [];
|
|
2483
|
-
if (targets.length > 0) {
|
|
2484
|
-
installOutcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
|
|
2485
|
-
}
|
|
2486
|
-
else if (hasArg('--no-install')) {
|
|
2487
|
-
printLine(' Skipped (--no-install).');
|
|
2488
|
-
}
|
|
2489
2997
|
printLine('');
|
|
2490
|
-
//
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (hasArg('--no-
|
|
2494
|
-
printLine(' Skipped (--no-
|
|
2495
|
-
}
|
|
2496
|
-
else if (workspaceExists()) {
|
|
2497
|
-
workspaceState = 'exists';
|
|
2498
|
-
printLine(` ${WORKSPACE_DIR_NAME}/ already initialized.`);
|
|
2998
|
+
// Step 3: global coding-agent skills
|
|
2999
|
+
let installOutcomes = [];
|
|
3000
|
+
printLine('Step 3: Install coding agent skills');
|
|
3001
|
+
if (hasArg('--no-install') || validateOnly || fix) {
|
|
3002
|
+
printLine(hasArg('--no-install') ? ' Skipped (--no-install).' : ' Skipped for validation/repair.');
|
|
2499
3003
|
}
|
|
2500
3004
|
else {
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
dryRun,
|
|
2508
|
-
});
|
|
2509
|
-
workspaceState = result.state === 'would-create' ? 'would-create' : result.state;
|
|
2510
|
-
for (const action of result.actions) {
|
|
2511
|
-
printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`);
|
|
3005
|
+
let targets = setupSkillTargetsFromArgs();
|
|
3006
|
+
if (targets.length === 0 && !noInput && !dryRun) {
|
|
3007
|
+
const choices = detectedSetupSkillChoices(setupSkillHome);
|
|
3008
|
+
if (choices.length > 0) {
|
|
3009
|
+
const selectedChoices = await promptKeyboardMultiSelect('Choose the coding agents that should learn how to use Orizu', choices, choice => choice.label, () => true);
|
|
3010
|
+
targets = selectedChoices.map(choice => choice.target);
|
|
2512
3011
|
}
|
|
3012
|
+
else {
|
|
3013
|
+
printLine(' No Codex or Claude Code agent folders were detected.');
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
if (targets.length === 0) {
|
|
3017
|
+
printLine(' Skipped skill install.');
|
|
2513
3018
|
}
|
|
2514
3019
|
else {
|
|
2515
|
-
printLine(
|
|
3020
|
+
printLine(' Installing skills...');
|
|
3021
|
+
installOutcomes = await applySkillInstallTargets(targets, {
|
|
3022
|
+
mode: installMode,
|
|
3023
|
+
skipConfirm,
|
|
3024
|
+
dryRun,
|
|
3025
|
+
cwd: workspaceRoot,
|
|
3026
|
+
homeDir: setupSkillHome,
|
|
3027
|
+
showPlan: false,
|
|
3028
|
+
printResults: false,
|
|
3029
|
+
});
|
|
3030
|
+
if (dryRun) {
|
|
3031
|
+
printLine(' Dry run: no skill files were changed.');
|
|
3032
|
+
}
|
|
3033
|
+
for (const outcome of installOutcomes) {
|
|
3034
|
+
const label = setupSkillLabel(outcome.target);
|
|
3035
|
+
if (outcome.action === 'failed') {
|
|
3036
|
+
printLine(` ${label} failed: ${outcome.error}`);
|
|
3037
|
+
}
|
|
3038
|
+
else if (outcome.action === 'skipped') {
|
|
3039
|
+
printLine(` ${label} skipped`);
|
|
3040
|
+
}
|
|
3041
|
+
else {
|
|
3042
|
+
printLine(` ${label} installed`);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
2516
3045
|
}
|
|
2517
3046
|
}
|
|
2518
3047
|
printLine('');
|
|
2519
3048
|
// Phase 4: coding-agent handoff
|
|
2520
3049
|
const handoffPrompt = renderAgentSetupPrompt({
|
|
2521
|
-
workspacePath:
|
|
2522
|
-
? getWorkspaceRoot()
|
|
3050
|
+
workspacePath: workspaceResult && workspaceState !== 'invalid'
|
|
3051
|
+
? getWorkspaceRoot(workspaceRoot)
|
|
2523
3052
|
: null,
|
|
2524
3053
|
});
|
|
2525
|
-
if (!hasArg('--no-handoff')) {
|
|
2526
|
-
printLine('4
|
|
3054
|
+
if (handoffRequested && !hasArg('--no-handoff')) {
|
|
3055
|
+
printLine('Step 4: Coding-agent handoff');
|
|
2527
3056
|
printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:');
|
|
2528
3057
|
printLine('');
|
|
2529
3058
|
printLine('--- prompt start ---');
|
|
@@ -2563,7 +3092,7 @@ async function setupCommand() {
|
|
|
2563
3092
|
const summaryTargets = installOutcomes.length > 0
|
|
2564
3093
|
? installOutcomes
|
|
2565
3094
|
.filter(outcome => outcome.action !== 'failed')
|
|
2566
|
-
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
|
|
3095
|
+
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target, { cwd: workspaceRoot, homeDir: setupSkillHome }) }))
|
|
2567
3096
|
: [];
|
|
2568
3097
|
const source = resolveSkillSource();
|
|
2569
3098
|
if (hasJsonFlag()) {
|
|
@@ -2582,29 +3111,45 @@ async function setupCommand() {
|
|
|
2582
3111
|
cliVersion: getCliVersion(),
|
|
2583
3112
|
skillHash: computeSkillContentHash(source.root),
|
|
2584
3113
|
},
|
|
2585
|
-
workspace: {
|
|
3114
|
+
workspace: {
|
|
3115
|
+
state: workspaceState,
|
|
3116
|
+
path: getWorkspaceRoot(workspaceRoot),
|
|
3117
|
+
validationLogPath,
|
|
3118
|
+
operations: workspaceResult?.operations.map(op => ({
|
|
3119
|
+
action: op.action,
|
|
3120
|
+
path: op.path,
|
|
3121
|
+
safe: op.safe,
|
|
3122
|
+
reason: op.reason,
|
|
3123
|
+
entries: op.entries,
|
|
3124
|
+
sourcePath: op.sourcePath,
|
|
3125
|
+
target: op.target,
|
|
3126
|
+
})) || [],
|
|
3127
|
+
findings: workspaceResult?.findings || [],
|
|
3128
|
+
},
|
|
2586
3129
|
});
|
|
2587
3130
|
return;
|
|
2588
3131
|
}
|
|
2589
3132
|
printLine('Setup summary');
|
|
2590
3133
|
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`);
|
|
2591
3134
|
if (summaryTargets.length > 0) {
|
|
2592
|
-
printLine('
|
|
3135
|
+
printLine(' Skills:');
|
|
2593
3136
|
for (const { outcome, status } of summaryTargets) {
|
|
2594
3137
|
const modeLabel = status.mode ? `, ${status.mode}` : '';
|
|
2595
3138
|
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`);
|
|
2596
3139
|
}
|
|
2597
3140
|
}
|
|
2598
3141
|
else {
|
|
2599
|
-
printLine('
|
|
3142
|
+
printLine(' Skills: none installed this run');
|
|
2600
3143
|
}
|
|
2601
3144
|
const failed = installOutcomes.filter(outcome => outcome.action === 'failed');
|
|
2602
3145
|
for (const failure of failed) {
|
|
2603
3146
|
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`);
|
|
2604
3147
|
}
|
|
2605
|
-
printLine(`
|
|
2606
|
-
|
|
2607
|
-
|
|
3148
|
+
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : getWorkspaceRoot(workspaceRoot)}`);
|
|
3149
|
+
if (workspaceResult) {
|
|
3150
|
+
printLine(` Validation: ${validationSummary(workspaceResult.findings, validationLogPath)}`);
|
|
3151
|
+
}
|
|
3152
|
+
printLine(' Next: run `orizu setup prompt` if you want a coding-agent handoff prompt.');
|
|
2608
3153
|
}
|
|
2609
3154
|
function setupPromptCommand() {
|
|
2610
3155
|
const prompt = renderAgentSetupPrompt({
|
|
@@ -2626,7 +3171,7 @@ function readJsonFile(pathArg) {
|
|
|
2626
3171
|
return parsed;
|
|
2627
3172
|
}
|
|
2628
3173
|
catch (error) {
|
|
2629
|
-
throw new Error(`Invalid JSON file '${pathArg}': ${
|
|
3174
|
+
throw new Error(`Invalid JSON file '${pathArg}': ${getErrorMessage(error)}`);
|
|
2630
3175
|
}
|
|
2631
3176
|
}
|
|
2632
3177
|
function isRecord(value) {
|
|
@@ -2690,126 +3235,6 @@ function writeTextFileEnsuringDir(pathArg, content) {
|
|
|
2690
3235
|
mkdirSync(dirname(pathArg), { recursive: true });
|
|
2691
3236
|
writeFileSync(pathArg, content);
|
|
2692
3237
|
}
|
|
2693
|
-
function shouldExcludeArtifactPath(relativePath) {
|
|
2694
|
-
const parts = relativePath.split('/');
|
|
2695
|
-
return parts.some(part => part === '.git' ||
|
|
2696
|
-
part === '.DS_Store' ||
|
|
2697
|
-
part === '__pycache__' ||
|
|
2698
|
-
part === '.pytest_cache');
|
|
2699
|
-
}
|
|
2700
|
-
function collectArtifactFiles(sourceDir, relativeDir = '') {
|
|
2701
|
-
const absoluteDir = relativeDir ? join(sourceDir, relativeDir) : sourceDir;
|
|
2702
|
-
return readdirSync(absoluteDir, { withFileTypes: true })
|
|
2703
|
-
.flatMap(entry => {
|
|
2704
|
-
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
2705
|
-
if (shouldExcludeArtifactPath(relativePath)) {
|
|
2706
|
-
return [];
|
|
2707
|
-
}
|
|
2708
|
-
if (entry.isDirectory()) {
|
|
2709
|
-
return collectArtifactFiles(sourceDir, relativePath);
|
|
2710
|
-
}
|
|
2711
|
-
return entry.isFile() ? [relativePath] : [];
|
|
2712
|
-
})
|
|
2713
|
-
.sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
2714
|
-
}
|
|
2715
|
-
const ZIP_GENERAL_PURPOSE_UTF8 = 0x0800;
|
|
2716
|
-
const ZIP_DOS_TIME_2000_01_01 = 0;
|
|
2717
|
-
const ZIP_DOS_DATE_2000_01_01 = ((2000 - 1980) << 9) | (1 << 5) | 1;
|
|
2718
|
-
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
2719
|
-
let value = index;
|
|
2720
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
2721
|
-
value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1);
|
|
2722
|
-
}
|
|
2723
|
-
return value >>> 0;
|
|
2724
|
-
});
|
|
2725
|
-
function crc32(bytes) {
|
|
2726
|
-
let crc = 0xffffffff;
|
|
2727
|
-
for (const byte of bytes) {
|
|
2728
|
-
crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
2729
|
-
}
|
|
2730
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
2731
|
-
}
|
|
2732
|
-
function createStoredZip(entries) {
|
|
2733
|
-
const localParts = [];
|
|
2734
|
-
const centralParts = [];
|
|
2735
|
-
let offset = 0;
|
|
2736
|
-
for (const entry of entries) {
|
|
2737
|
-
const name = Buffer.from(entry.path, 'utf8');
|
|
2738
|
-
const crc = crc32(entry.data);
|
|
2739
|
-
const localHeader = Buffer.alloc(30);
|
|
2740
|
-
localHeader.writeUInt32LE(0x04034b50, 0);
|
|
2741
|
-
localHeader.writeUInt16LE(10, 4);
|
|
2742
|
-
localHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 6);
|
|
2743
|
-
localHeader.writeUInt16LE(0, 8);
|
|
2744
|
-
localHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 10);
|
|
2745
|
-
localHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 12);
|
|
2746
|
-
localHeader.writeUInt32LE(crc, 14);
|
|
2747
|
-
localHeader.writeUInt32LE(entry.data.length, 18);
|
|
2748
|
-
localHeader.writeUInt32LE(entry.data.length, 22);
|
|
2749
|
-
localHeader.writeUInt16LE(name.length, 26);
|
|
2750
|
-
localHeader.writeUInt16LE(0, 28);
|
|
2751
|
-
localParts.push(localHeader, name, entry.data);
|
|
2752
|
-
const centralHeader = Buffer.alloc(46);
|
|
2753
|
-
centralHeader.writeUInt32LE(0x02014b50, 0);
|
|
2754
|
-
centralHeader.writeUInt16LE(20, 4);
|
|
2755
|
-
centralHeader.writeUInt16LE(10, 6);
|
|
2756
|
-
centralHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 8);
|
|
2757
|
-
centralHeader.writeUInt16LE(0, 10);
|
|
2758
|
-
centralHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 12);
|
|
2759
|
-
centralHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 14);
|
|
2760
|
-
centralHeader.writeUInt32LE(crc, 16);
|
|
2761
|
-
centralHeader.writeUInt32LE(entry.data.length, 20);
|
|
2762
|
-
centralHeader.writeUInt32LE(entry.data.length, 24);
|
|
2763
|
-
centralHeader.writeUInt16LE(name.length, 28);
|
|
2764
|
-
centralHeader.writeUInt16LE(0, 30);
|
|
2765
|
-
centralHeader.writeUInt16LE(0, 32);
|
|
2766
|
-
centralHeader.writeUInt16LE(0, 34);
|
|
2767
|
-
centralHeader.writeUInt16LE(0, 36);
|
|
2768
|
-
centralHeader.writeUInt32LE(0, 38);
|
|
2769
|
-
centralHeader.writeUInt32LE(offset, 42);
|
|
2770
|
-
centralParts.push(centralHeader, name);
|
|
2771
|
-
offset += localHeader.length + name.length + entry.data.length;
|
|
2772
|
-
}
|
|
2773
|
-
const centralDirectoryOffset = offset;
|
|
2774
|
-
const centralDirectory = Buffer.concat(centralParts);
|
|
2775
|
-
const endOfCentralDirectory = Buffer.alloc(22);
|
|
2776
|
-
endOfCentralDirectory.writeUInt32LE(0x06054b50, 0);
|
|
2777
|
-
endOfCentralDirectory.writeUInt16LE(0, 4);
|
|
2778
|
-
endOfCentralDirectory.writeUInt16LE(0, 6);
|
|
2779
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 8);
|
|
2780
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 10);
|
|
2781
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12);
|
|
2782
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectoryOffset, 16);
|
|
2783
|
-
endOfCentralDirectory.writeUInt16LE(0, 20);
|
|
2784
|
-
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory]);
|
|
2785
|
-
}
|
|
2786
|
-
function zipDirectoryToBase64(dirArg) {
|
|
2787
|
-
const sourceDir = expandHomePath(dirArg);
|
|
2788
|
-
try {
|
|
2789
|
-
const stats = statSync(sourceDir);
|
|
2790
|
-
if (!stats.isDirectory()) {
|
|
2791
|
-
throw new Error(`${sourceDir} is not a directory`);
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
catch (error) {
|
|
2795
|
-
if (error?.code === 'ENOENT') {
|
|
2796
|
-
throw new Error(`Directory not found: ${sourceDir}`);
|
|
2797
|
-
}
|
|
2798
|
-
throw error;
|
|
2799
|
-
}
|
|
2800
|
-
const files = collectArtifactFiles(sourceDir);
|
|
2801
|
-
if (files.length === 0) {
|
|
2802
|
-
throw new Error(`Directory contains no artifact files: ${sourceDir}`);
|
|
2803
|
-
}
|
|
2804
|
-
const bytes = createStoredZip(files.map(relativePath => ({
|
|
2805
|
-
path: relativePath,
|
|
2806
|
-
data: readFileSync(join(sourceDir, relativePath)),
|
|
2807
|
-
})));
|
|
2808
|
-
return {
|
|
2809
|
-
zipBase64: bytes.toString('base64'),
|
|
2810
|
-
contentSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
2811
|
-
};
|
|
2812
|
-
}
|
|
2813
3238
|
function readManifestFile(dirArg) {
|
|
2814
3239
|
return readJsonFile(join(expandHomePath(dirArg), 'manifest.json'));
|
|
2815
3240
|
}
|
|
@@ -2986,39 +3411,89 @@ function parseCommaSeparated(value) {
|
|
|
2986
3411
|
.map(item => item.trim())
|
|
2987
3412
|
.filter(Boolean);
|
|
2988
3413
|
}
|
|
3414
|
+
function readAssignmentManifest(path) {
|
|
3415
|
+
if (!path) {
|
|
3416
|
+
return null;
|
|
3417
|
+
}
|
|
3418
|
+
return readAssignmentManifestJsonlFile(expandHomePath(path));
|
|
3419
|
+
}
|
|
3420
|
+
function readOptionalTaskTextArg(valueFlag, fileFlag) {
|
|
3421
|
+
const value = getArg(valueFlag);
|
|
3422
|
+
const file = getArg(fileFlag);
|
|
3423
|
+
rejectDashPrefixedOptionValue(valueFlag, value);
|
|
3424
|
+
rejectDashPrefixedOptionValue(fileFlag, file);
|
|
3425
|
+
if (value !== null && file !== null) {
|
|
3426
|
+
throw new Error(`Use either ${valueFlag} or ${fileFlag}, not both.`);
|
|
3427
|
+
}
|
|
3428
|
+
if (file !== null) {
|
|
3429
|
+
return readSourceFile(file);
|
|
3430
|
+
}
|
|
3431
|
+
return value ?? undefined;
|
|
3432
|
+
}
|
|
3433
|
+
function readPositiveIntegerFlag(name) {
|
|
3434
|
+
const value = getArg(name);
|
|
3435
|
+
rejectDashPrefixedOptionValue(name, value);
|
|
3436
|
+
if (value === null) {
|
|
3437
|
+
return undefined;
|
|
3438
|
+
}
|
|
3439
|
+
const parsed = Number(value);
|
|
3440
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
3441
|
+
throw new Error(`${name} must be a positive integer`);
|
|
3442
|
+
}
|
|
3443
|
+
return parsed;
|
|
3444
|
+
}
|
|
2989
3445
|
async function createTask() {
|
|
2990
3446
|
const projectSlug = getArg('--project');
|
|
2991
3447
|
const datasetId = getArg('--dataset');
|
|
2992
3448
|
const appId = getArg('--app');
|
|
2993
3449
|
const title = getArg('--title');
|
|
2994
3450
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
3451
|
+
const assignmentFile = getArg('--assignment-file');
|
|
2995
3452
|
const publish = hasArg('--publish');
|
|
2996
3453
|
const versionArg = getArg('--version');
|
|
2997
3454
|
const instructions = getArg('--instructions');
|
|
2998
3455
|
const labelsPerItemArg = getArg('--labels-per-item');
|
|
2999
|
-
const
|
|
3456
|
+
const parsedLabelsPerItem = labelsPerItemArg ? Number(labelsPerItemArg) : 1;
|
|
3457
|
+
const labelsPerItem = Number.isInteger(parsedLabelsPerItem) && parsedLabelsPerItem > 0
|
|
3458
|
+
? parsedLabelsPerItem
|
|
3459
|
+
: null;
|
|
3000
3460
|
const parsedVersionNum = versionArg ? Number(versionArg) : Number.NaN;
|
|
3001
3461
|
const versionNum = Number.isInteger(parsedVersionNum) && parsedVersionNum > 0 ? parsedVersionNum : null;
|
|
3002
|
-
if (
|
|
3003
|
-
throw new Error('
|
|
3462
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3463
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3464
|
+
}
|
|
3465
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3466
|
+
if (!projectSlug || !datasetId || !appId || !title || (publish && assignees.length === 0 && !explicitAssignments)) {
|
|
3467
|
+
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]');
|
|
3004
3468
|
}
|
|
3005
3469
|
if (versionArg && versionNum === null) {
|
|
3006
3470
|
throw new Error('--version must be a positive integer');
|
|
3007
3471
|
}
|
|
3472
|
+
if (labelsPerItemArg && labelsPerItem === null) {
|
|
3473
|
+
throw new Error('--labels-per-item must be a positive integer');
|
|
3474
|
+
}
|
|
3475
|
+
const requestBody = {
|
|
3476
|
+
projectSlug,
|
|
3477
|
+
datasetId,
|
|
3478
|
+
appId,
|
|
3479
|
+
versionNum,
|
|
3480
|
+
title,
|
|
3481
|
+
publish,
|
|
3482
|
+
instructions,
|
|
3483
|
+
};
|
|
3484
|
+
if (explicitAssignments) {
|
|
3485
|
+
requestBody.explicitAssignments = explicitAssignments;
|
|
3486
|
+
}
|
|
3487
|
+
else {
|
|
3488
|
+
requestBody.memberIds = assignees;
|
|
3489
|
+
}
|
|
3490
|
+
if (!explicitAssignments || labelsPerItemArg) {
|
|
3491
|
+
requestBody.requiredAssignmentsPerRow = labelsPerItem;
|
|
3492
|
+
}
|
|
3008
3493
|
const response = await authedFetch('/api/cli/tasks', {
|
|
3009
3494
|
method: 'POST',
|
|
3010
3495
|
headers: { 'Content-Type': 'application/json' },
|
|
3011
|
-
body: JSON.stringify(
|
|
3012
|
-
projectSlug,
|
|
3013
|
-
datasetId,
|
|
3014
|
-
appId,
|
|
3015
|
-
versionNum,
|
|
3016
|
-
title,
|
|
3017
|
-
memberIds: assignees,
|
|
3018
|
-
publish,
|
|
3019
|
-
instructions,
|
|
3020
|
-
requiredAssignmentsPerRow: labelsPerItem,
|
|
3021
|
-
}),
|
|
3496
|
+
body: JSON.stringify(requestBody),
|
|
3022
3497
|
});
|
|
3023
3498
|
if (!response.ok) {
|
|
3024
3499
|
const cliError = await formatTaskCreateError(response);
|
|
@@ -3041,13 +3516,16 @@ async function createTask() {
|
|
|
3041
3516
|
taskUrl: `${getBaseUrl()}/d/${projectSlug}/tasks/${data.task.id}`,
|
|
3042
3517
|
title: data.task.title,
|
|
3043
3518
|
status: data.task.status,
|
|
3519
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3044
3520
|
requiredAssignmentsPerRow: data.task.requiredAssignmentsPerRow,
|
|
3045
3521
|
assignmentsCreated: data.assignmentsCreated,
|
|
3046
3522
|
draft: data.task.status === 'draft',
|
|
3047
3523
|
...(data.task.status === 'draft'
|
|
3048
3524
|
? {
|
|
3049
3525
|
message: 'Draft created. Test it manually before assigning.',
|
|
3050
|
-
publishCommand:
|
|
3526
|
+
publishCommand: explicitAssignments
|
|
3527
|
+
? `orizu tasks publish --task ${data.task.id} --assignment-file ${assignmentFile ? shellQuote(assignmentFile) : '<path>'}`
|
|
3528
|
+
: `orizu tasks publish --task ${data.task.id} --assignees <userId1,userId2>`,
|
|
3051
3529
|
}
|
|
3052
3530
|
: {}),
|
|
3053
3531
|
...(data.assignmentShortfall !== undefined ? { assignmentShortfall: data.assignmentShortfall } : {}),
|
|
@@ -3062,24 +3540,155 @@ async function createTask() {
|
|
|
3062
3540
|
`\n Task ID: ${sanitizeTerminalText(data.task.id)}` +
|
|
3063
3541
|
`\n Dataset ID: ${sanitizeTerminalText(datasetId)}` +
|
|
3064
3542
|
`\n Version: v${data.task.versionNum} (${sanitizeTerminalText(data.task.versionId)})` +
|
|
3543
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3065
3544
|
`\n Labels/row: ${data.task.requiredAssignmentsPerRow}` +
|
|
3066
3545
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
3067
3546
|
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '') +
|
|
3068
3547
|
`\n URL: ${sanitizeTerminalText(taskUrl)}` +
|
|
3069
3548
|
(isDraft
|
|
3070
|
-
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval:
|
|
3549
|
+
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval: ${explicitAssignments
|
|
3550
|
+
? `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignment-file ${sanitizeTerminalText(assignmentFile || '<path>')}`
|
|
3551
|
+
: `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`}`
|
|
3071
3552
|
: ''));
|
|
3072
3553
|
}
|
|
3554
|
+
async function updateTask() {
|
|
3555
|
+
const taskId = getArg('--task');
|
|
3556
|
+
rejectDashPrefixedOptionValue('--task', taskId);
|
|
3557
|
+
if (!taskId) {
|
|
3558
|
+
throw new Error('Usage: orizu tasks update --task <taskId> [--title <text>] [--description <text>|--description-file <path>] [--instructions <text>|--instructions-file <path>] [--dataset <datasetId>] [--app <appId> [--version <n>]] [--labels-per-item <n>] [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--json]');
|
|
3559
|
+
}
|
|
3560
|
+
const title = getArg('--title');
|
|
3561
|
+
const description = readOptionalTaskTextArg('--description', '--description-file');
|
|
3562
|
+
const instructions = readOptionalTaskTextArg('--instructions', '--instructions-file');
|
|
3563
|
+
const datasetId = getArg('--dataset');
|
|
3564
|
+
const appId = getArg('--app');
|
|
3565
|
+
const versionNum = readPositiveIntegerFlag('--version');
|
|
3566
|
+
const requiredAssignmentsPerRow = readPositiveIntegerFlag('--labels-per-item');
|
|
3567
|
+
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
3568
|
+
const assignmentFile = getArg('--assignment-file');
|
|
3569
|
+
rejectDashPrefixedOptionValue('--title', title);
|
|
3570
|
+
rejectDashPrefixedOptionValue('--dataset', datasetId);
|
|
3571
|
+
rejectDashPrefixedOptionValue('--app', appId);
|
|
3572
|
+
rejectDashPrefixedOptionValue('--assignment-file', assignmentFile);
|
|
3573
|
+
if (title !== null && title.trim().length === 0) {
|
|
3574
|
+
throw new Error('--title must be a non-empty string');
|
|
3575
|
+
}
|
|
3576
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3577
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3578
|
+
}
|
|
3579
|
+
if (hasArg('--assignees') && assignees.length === 0) {
|
|
3580
|
+
throw new Error('--assignees requires at least one user ID or email');
|
|
3581
|
+
}
|
|
3582
|
+
if (versionNum !== undefined && !appId) {
|
|
3583
|
+
throw new Error('--version requires --app');
|
|
3584
|
+
}
|
|
3585
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3586
|
+
const requestBody = {};
|
|
3587
|
+
if (title !== null)
|
|
3588
|
+
requestBody.title = title.trim();
|
|
3589
|
+
if (description !== undefined)
|
|
3590
|
+
requestBody.description = description;
|
|
3591
|
+
if (instructions !== undefined)
|
|
3592
|
+
requestBody.instructions = instructions;
|
|
3593
|
+
if (datasetId !== null)
|
|
3594
|
+
requestBody.datasetId = datasetId;
|
|
3595
|
+
if (appId !== null)
|
|
3596
|
+
requestBody.appId = appId;
|
|
3597
|
+
if (versionNum !== undefined)
|
|
3598
|
+
requestBody.versionNum = versionNum;
|
|
3599
|
+
if (requiredAssignmentsPerRow !== undefined) {
|
|
3600
|
+
requestBody.requiredAssignmentsPerRow = requiredAssignmentsPerRow;
|
|
3601
|
+
}
|
|
3602
|
+
if (explicitAssignments) {
|
|
3603
|
+
requestBody.explicitAssignments = explicitAssignments;
|
|
3604
|
+
}
|
|
3605
|
+
else if (hasArg('--assignees')) {
|
|
3606
|
+
requestBody.memberIds = assignees;
|
|
3607
|
+
}
|
|
3608
|
+
if (Object.keys(requestBody).length === 0) {
|
|
3609
|
+
throw new Error('Usage: orizu tasks update --task <taskId> [--title <text>] [--description <text>|--description-file <path>] [--instructions <text>|--instructions-file <path>] [--dataset <datasetId>] [--app <appId> [--version <n>]] [--labels-per-item <n>] [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--json]');
|
|
3610
|
+
}
|
|
3611
|
+
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}`, {
|
|
3612
|
+
method: 'PATCH',
|
|
3613
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3614
|
+
body: JSON.stringify(requestBody),
|
|
3615
|
+
});
|
|
3616
|
+
if (!response.ok) {
|
|
3617
|
+
throw new Error(`Failed to update task: ${await extractErrorMessage(response)}`);
|
|
3618
|
+
}
|
|
3619
|
+
const data = await parseJsonResponse(response, 'Task update');
|
|
3620
|
+
if (hasJsonFlag()) {
|
|
3621
|
+
printJson({
|
|
3622
|
+
taskId: data.task.id,
|
|
3623
|
+
title: data.task.title ?? null,
|
|
3624
|
+
status: data.task.status ?? null,
|
|
3625
|
+
datasetId: data.task.dataset_id ?? null,
|
|
3626
|
+
appId: data.task.app_id ?? null,
|
|
3627
|
+
versionId: data.task.version_id ?? null,
|
|
3628
|
+
requiredAssignmentsPerRow: data.task.required_assignments_per_row ?? null,
|
|
3629
|
+
...(data.assignmentMode ? { assignmentMode: data.assignmentMode } : {}),
|
|
3630
|
+
...(data.assignmentsCreated !== undefined
|
|
3631
|
+
? { assignmentsCreated: data.assignmentsCreated }
|
|
3632
|
+
: {}),
|
|
3633
|
+
});
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
printLine(`Updated draft task ${sanitizeTerminalText(data.task.title || data.task.id)} (${sanitizeTerminalText(data.task.id)})` +
|
|
3637
|
+
(data.task.status ? ` [${sanitizeTerminalText(data.task.status)}]` : '') +
|
|
3638
|
+
(data.task.dataset_id ? `\n Dataset ID: ${sanitizeTerminalText(data.task.dataset_id)}` : '') +
|
|
3639
|
+
(data.task.version_id ? `\n Version ID: ${sanitizeTerminalText(data.task.version_id)}` : '') +
|
|
3640
|
+
(data.task.required_assignments_per_row
|
|
3641
|
+
? `\n Labels/row: ${data.task.required_assignments_per_row}`
|
|
3642
|
+
: '') +
|
|
3643
|
+
(data.assignmentMode ? `\n Mode: ${sanitizeTerminalText(data.assignmentMode === 'custom' ? 'custom row map' : 'auto distribute')}` : '') +
|
|
3644
|
+
(data.assignmentsCreated !== undefined ? `\n Assignments: ${data.assignmentsCreated}` : ''));
|
|
3645
|
+
}
|
|
3646
|
+
async function discardTask() {
|
|
3647
|
+
const taskId = getArg('--task');
|
|
3648
|
+
rejectDashPrefixedOptionValue('--task', taskId);
|
|
3649
|
+
if (!taskId) {
|
|
3650
|
+
throw new Error('Usage: orizu tasks discard --task <taskId> [--yes] [--json]');
|
|
3651
|
+
}
|
|
3652
|
+
if (!hasArg('--yes')) {
|
|
3653
|
+
if (!isInteractiveTerminal()) {
|
|
3654
|
+
throw new Error('Refusing to discard a draft in non-interactive mode without --yes');
|
|
3655
|
+
}
|
|
3656
|
+
const confirmed = await askYesNo(`Discard draft task ${taskId}? This cannot be undone.`, false);
|
|
3657
|
+
if (!confirmed) {
|
|
3658
|
+
printLine('Discard cancelled.');
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}`, {
|
|
3663
|
+
method: 'DELETE',
|
|
3664
|
+
});
|
|
3665
|
+
if (!response.ok) {
|
|
3666
|
+
throw new Error(`Failed to discard draft task: ${await extractErrorMessage(response)}`);
|
|
3667
|
+
}
|
|
3668
|
+
const data = await parseJsonResponse(response, 'Task discard');
|
|
3669
|
+
if (hasJsonFlag()) {
|
|
3670
|
+
printJson({ taskId, discarded: data.success });
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
printLine(`Discarded draft task ${sanitizeTerminalText(taskId)}.`);
|
|
3674
|
+
}
|
|
3073
3675
|
async function publishTask() {
|
|
3074
3676
|
const taskId = getArg('--task');
|
|
3075
3677
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
3076
|
-
|
|
3077
|
-
|
|
3678
|
+
const assignmentFile = getArg('--assignment-file');
|
|
3679
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3680
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3681
|
+
}
|
|
3682
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3683
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
3684
|
+
throw new Error('Usage: orizu tasks publish --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--json]');
|
|
3078
3685
|
}
|
|
3079
3686
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/publish`, {
|
|
3080
3687
|
method: 'POST',
|
|
3081
3688
|
headers: { 'Content-Type': 'application/json' },
|
|
3082
|
-
body: JSON.stringify(
|
|
3689
|
+
body: JSON.stringify(explicitAssignments
|
|
3690
|
+
? { explicitAssignments }
|
|
3691
|
+
: { memberIds: assignees }),
|
|
3083
3692
|
});
|
|
3084
3693
|
if (!response.ok) {
|
|
3085
3694
|
throw new Error(`Failed to publish task: ${await extractErrorMessage(response)}`);
|
|
@@ -3089,6 +3698,7 @@ async function publishTask() {
|
|
|
3089
3698
|
printLine(JSON.stringify({
|
|
3090
3699
|
taskId: data.task.id,
|
|
3091
3700
|
status: data.task.status,
|
|
3701
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3092
3702
|
assignmentsCreated: data.assignmentsCreated,
|
|
3093
3703
|
assignmentsProcessed: data.assignmentsProcessed,
|
|
3094
3704
|
...(data.assignmentsRemoved !== undefined
|
|
@@ -3102,6 +3712,7 @@ async function publishTask() {
|
|
|
3102
3712
|
return;
|
|
3103
3713
|
}
|
|
3104
3714
|
printLine(`Published task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]` +
|
|
3715
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3105
3716
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
3106
3717
|
(data.assignmentsRemoved !== undefined
|
|
3107
3718
|
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
@@ -3111,23 +3722,50 @@ async function publishTask() {
|
|
|
3111
3722
|
async function assignTask() {
|
|
3112
3723
|
const taskId = getArg('--task');
|
|
3113
3724
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
3114
|
-
|
|
3115
|
-
|
|
3725
|
+
const assignmentFile = getArg('--assignment-file');
|
|
3726
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3727
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3728
|
+
}
|
|
3729
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3730
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
3731
|
+
throw new Error('Usage: orizu tasks assign --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--replace-existing] [--json]');
|
|
3732
|
+
}
|
|
3733
|
+
const requestBody = explicitAssignments
|
|
3734
|
+
? { explicitAssignments }
|
|
3735
|
+
: { memberIds: assignees };
|
|
3736
|
+
if (hasArg('--replace-existing')) {
|
|
3737
|
+
requestBody.replaceExisting = true;
|
|
3116
3738
|
}
|
|
3117
3739
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/assign`, {
|
|
3118
3740
|
method: 'POST',
|
|
3119
3741
|
headers: { 'Content-Type': 'application/json' },
|
|
3120
|
-
body: JSON.stringify(
|
|
3742
|
+
body: JSON.stringify(requestBody),
|
|
3121
3743
|
});
|
|
3122
3744
|
if (!response.ok) {
|
|
3123
3745
|
throw new Error(`Failed to assign task: ${await response.text()}`);
|
|
3124
3746
|
}
|
|
3125
3747
|
const data = await parseJsonResponse(response, 'Task assign');
|
|
3126
3748
|
if (hasJsonFlag()) {
|
|
3127
|
-
printJson({
|
|
3749
|
+
printJson({
|
|
3750
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3751
|
+
assignmentsProcessed: data.assignmentsProcessed ?? data.assignmentsCreated,
|
|
3752
|
+
assignmentsCreated: data.assignmentsCreated,
|
|
3753
|
+
...(data.assignmentsRemoved !== undefined
|
|
3754
|
+
? { assignmentsRemoved: data.assignmentsRemoved }
|
|
3755
|
+
: {}),
|
|
3756
|
+
...(data.assignmentShortfall !== undefined
|
|
3757
|
+
? { assignmentShortfall: data.assignmentShortfall }
|
|
3758
|
+
: {}),
|
|
3759
|
+
...(data.warning ? { warning: data.warning } : {}),
|
|
3760
|
+
});
|
|
3128
3761
|
return;
|
|
3129
3762
|
}
|
|
3130
|
-
printLine(`Created ${data.assignmentsCreated} assignments.`
|
|
3763
|
+
printLine(`Created ${data.assignmentsCreated} assignments.` +
|
|
3764
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3765
|
+
(data.assignmentsRemoved !== undefined
|
|
3766
|
+
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
3767
|
+
: '') +
|
|
3768
|
+
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : ''));
|
|
3131
3769
|
}
|
|
3132
3770
|
async function taskStatus() {
|
|
3133
3771
|
const taskId = getArg('--task');
|
|
@@ -3172,9 +3810,9 @@ async function taskStatus() {
|
|
|
3172
3810
|
}
|
|
3173
3811
|
async function updateTaskStatus(targetStatus) {
|
|
3174
3812
|
const taskId = getArg('--task');
|
|
3813
|
+
const labels = TASK_STATUS_ACTION_LABELS[targetStatus];
|
|
3175
3814
|
if (!taskId) {
|
|
3176
|
-
|
|
3177
|
-
throw new Error(`Usage: orizu tasks ${verb} --task <taskId>`);
|
|
3815
|
+
throw new Error(`Usage: orizu tasks ${labels.verb} --task <taskId>`);
|
|
3178
3816
|
}
|
|
3179
3817
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`, {
|
|
3180
3818
|
method: 'PATCH',
|
|
@@ -3182,16 +3820,14 @@ async function updateTaskStatus(targetStatus) {
|
|
|
3182
3820
|
body: JSON.stringify({ status: targetStatus }),
|
|
3183
3821
|
});
|
|
3184
3822
|
if (!response.ok) {
|
|
3185
|
-
|
|
3186
|
-
throw new Error(`Failed to ${verb} task: ${await response.text()}`);
|
|
3823
|
+
throw new Error(`Failed to ${labels.verb} task: ${await response.text()}`);
|
|
3187
3824
|
}
|
|
3188
3825
|
const data = await parseJsonResponse(response, 'Task status update');
|
|
3189
3826
|
if (hasJsonFlag()) {
|
|
3190
3827
|
printJson({ task: data.task });
|
|
3191
3828
|
return;
|
|
3192
3829
|
}
|
|
3193
|
-
|
|
3194
|
-
printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
|
|
3830
|
+
printLine(`${labels.pastTense} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
|
|
3195
3831
|
}
|
|
3196
3832
|
async function setTaskReport() {
|
|
3197
3833
|
const taskId = getArg('--task');
|
|
@@ -3367,10 +4003,10 @@ async function changeTeamMemberRole() {
|
|
|
3367
4003
|
const email = getArg('--email');
|
|
3368
4004
|
const role = getArg('--role');
|
|
3369
4005
|
if (!teamSlug || !email || !role) {
|
|
3370
|
-
throw new Error('Usage: orizu teams members role --team <teamSlug> --email <email> --role <admin|
|
|
4006
|
+
throw new Error('Usage: orizu teams members role --team <teamSlug> --email <email> --role <admin|curator|judge>');
|
|
3371
4007
|
}
|
|
3372
|
-
if (!['admin', '
|
|
3373
|
-
throw new Error('role must be one of: admin,
|
|
4008
|
+
if (!['admin', 'curator', 'judge'].includes(role)) {
|
|
4009
|
+
throw new Error('role must be one of: admin, curator, judge');
|
|
3374
4010
|
}
|
|
3375
4011
|
const members = await fetchTeamMembers(teamSlug);
|
|
3376
4012
|
const member = members.find(item => item.email.toLowerCase() === email.toLowerCase());
|
|
@@ -4129,12 +4765,16 @@ async function runnersExec() {
|
|
|
4129
4765
|
throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(boundedRunnerOutput(result.stderr || result.stdout))}`);
|
|
4130
4766
|
}
|
|
4131
4767
|
const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8'));
|
|
4768
|
+
// Spread runner output first: the scorer/prompt context identity fields
|
|
4769
|
+
// (row_id, version ids, metric_key) are authoritative, never runner-overridable.
|
|
4132
4770
|
resultLines.push(JSON.stringify({
|
|
4771
|
+
...runnerOutput,
|
|
4133
4772
|
row_id: row.id,
|
|
4134
4773
|
prompt_version_id: context.prompt.promptVersionId,
|
|
4135
4774
|
runner_version_id: context.prompt.runnerVersionId,
|
|
4136
|
-
...(scorerVersion
|
|
4137
|
-
|
|
4775
|
+
...(scorerVersion
|
|
4776
|
+
? { scorer_version_id: context.scorer?.versionId || scorerVersion, metric_key: context.scorer?.metricKey || 'score' }
|
|
4777
|
+
: {}),
|
|
4138
4778
|
}));
|
|
4139
4779
|
}
|
|
4140
4780
|
finally {
|
|
@@ -4434,6 +5074,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4434
5074
|
await createTask();
|
|
4435
5075
|
return;
|
|
4436
5076
|
}
|
|
5077
|
+
if (command === 'tasks' && subcommand === 'update') {
|
|
5078
|
+
await updateTask();
|
|
5079
|
+
return;
|
|
5080
|
+
}
|
|
5081
|
+
if (command === 'tasks' && subcommand === 'discard') {
|
|
5082
|
+
await discardTask();
|
|
5083
|
+
return;
|
|
5084
|
+
}
|
|
4437
5085
|
if (command === 'tasks' && subcommand === 'publish') {
|
|
4438
5086
|
await publishTask();
|
|
4439
5087
|
return;
|
|
@@ -4460,6 +5108,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4460
5108
|
await updateTaskStatus('active');
|
|
4461
5109
|
return;
|
|
4462
5110
|
}
|
|
5111
|
+
if (command === 'tasks' && subcommand === 'complete') {
|
|
5112
|
+
await updateTaskStatus('completed');
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
4463
5115
|
if (command === 'datasets' && subcommand === 'upload') {
|
|
4464
5116
|
await uploadDataset();
|
|
4465
5117
|
return;
|
|
@@ -4513,6 +5165,41 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4513
5165
|
await downloadAnnotations();
|
|
4514
5166
|
return;
|
|
4515
5167
|
}
|
|
5168
|
+
if (command === 'internal' || (command === 'session' && subcommand === 'start' && hasArg('--hosted'))) {
|
|
5169
|
+
process.exitCode = await hostedCommand(cliArgs, { json: hasJsonFlag(), print: printLine, printErr: printError });
|
|
5170
|
+
return;
|
|
5171
|
+
}
|
|
5172
|
+
if (command === 'session' || command === 'run') {
|
|
5173
|
+
process.exitCode = await workbenchCommand(cliArgs, { json: hasJsonFlag(), print: printLine });
|
|
5174
|
+
return;
|
|
5175
|
+
}
|
|
5176
|
+
if (command === 'workspace') {
|
|
5177
|
+
process.exitCode = await workspaceSyncCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine });
|
|
5178
|
+
return;
|
|
5179
|
+
}
|
|
5180
|
+
if (command === 'git-credential') {
|
|
5181
|
+
process.exitCode = await runGitCredential(subcommand ?? 'get', { stdin: readFileSync(0, 'utf8'), cwd: process.cwd(), print: printLine, printErr: printError });
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
if (command === 'github' && subcommand === 'link') {
|
|
5185
|
+
const noInput = hasArg('--no-input') || hasArg('--non-interactive') || !isInteractiveTerminal();
|
|
5186
|
+
const team = getOptionalArgValue('--team') || (await resolveSetupTeam(null, noInput, false)).slug;
|
|
5187
|
+
await runGithubLink(team, { print: printLine, fetcher: authedFetch, openUrl: openInBrowser, confirm: (org) => askYesNo(`Connect this team to GitHub org '${org}'?`, true) });
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
5190
|
+
if (command === 'connectors' || command === 'manifests') {
|
|
5191
|
+
const moduleCommand = command === 'connectors' ? connectorsCommand : manifestsCommand;
|
|
5192
|
+
process.exitCode = await moduleCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, resolveProjectSlug });
|
|
5193
|
+
return;
|
|
5194
|
+
}
|
|
5195
|
+
if (command === 'team' && (subcommand === 'kill-agents' || subcommand === 'release-agents')) {
|
|
5196
|
+
process.exitCode = await killSwitchCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, printErr: printError });
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
if (command === 'team' && subcommand === 'egress-allowlist') {
|
|
5200
|
+
process.exitCode = await egressAllowlistCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, printErr: printError });
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
4516
5203
|
printUsage();
|
|
4517
5204
|
process.exit(1);
|
|
4518
5205
|
}
|
|
@@ -4530,7 +5217,7 @@ function isCliEntrypoint() {
|
|
|
4530
5217
|
}
|
|
4531
5218
|
if (isCliEntrypoint()) {
|
|
4532
5219
|
main().catch(error => {
|
|
4533
|
-
|
|
5220
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
4534
5221
|
process.exit(1);
|
|
4535
5222
|
});
|
|
4536
5223
|
}
|