orizu 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/artifact-archive.js +152 -0
- package/dist/connectors-cli.js +80 -0
- package/dist/credentials.js +4 -1
- package/dist/daytona-slice-rehearsal.js +255 -0
- package/dist/daytona-slice.js +441 -0
- package/dist/egress-allowlist-cli.js +132 -0
- package/dist/egress-policy.js +159 -0
- package/dist/git-credential.js +131 -0
- package/dist/github-setup.js +226 -0
- package/dist/help.js +271 -16
- package/dist/hosted-bootstrap-rehearsal.js +235 -0
- package/dist/hosted-bootstrap.js +365 -0
- package/dist/hosted-broker-server.js +220 -0
- package/dist/hosted-git-http-server.js +136 -0
- package/dist/hosted-harness-claude.js +493 -0
- package/dist/hosted-harness-opencode.js +729 -0
- package/dist/hosted-harness.js +60 -0
- package/dist/hosted-loop.js +477 -0
- package/dist/hosted-run-event-sink.js +499 -0
- package/dist/hosted-runtime-assets.js +216 -0
- package/dist/hosted-session-cli.js +642 -0
- package/dist/index.js +855 -237
- package/dist/kill-switch-cli.js +89 -0
- package/dist/manifests-cli.js +143 -0
- package/dist/preview-runtime.js +4 -1
- package/dist/sandbox-provider.js +238 -0
- package/dist/secret-redaction.js +112 -0
- package/dist/session-timeline.js +103 -0
- package/dist/setup-prompt.js +4 -2
- package/dist/skill-installer.js +9 -1
- package/dist/smoke.js +250 -0
- package/dist/task-assignment-manifest.js +39 -2
- package/dist/vercel-sandbox-provider.js +260 -0
- package/dist/workbench-cli.js +493 -0
- package/dist/workspace-sync.js +924 -0
- package/dist/workspace.js +868 -57
- package/package.json +4 -1
- package/src/artifact-archive.ts +182 -0
- package/src/connectors-cli.ts +115 -0
- package/src/credentials.ts +6 -2
- package/src/daytona-slice-rehearsal.ts +320 -0
- package/src/daytona-slice.ts +598 -0
- package/src/egress-allowlist-cli.ts +147 -0
- package/src/egress-policy.ts +188 -0
- package/src/git-credential.ts +155 -0
- package/src/github-setup.ts +366 -0
- package/src/help.ts +272 -16
- package/src/hosted-bootstrap-rehearsal.ts +312 -0
- package/src/hosted-bootstrap.ts +571 -0
- package/src/hosted-broker-server.ts +278 -0
- package/src/hosted-git-http-server.ts +164 -0
- package/src/hosted-harness-claude.ts +621 -0
- package/src/hosted-harness-opencode.ts +823 -0
- package/src/hosted-harness.ts +124 -0
- package/src/hosted-loop.ts +680 -0
- package/src/hosted-run-event-sink.ts +632 -0
- package/src/hosted-runtime-assets.ts +272 -0
- package/src/hosted-session-cli.ts +844 -0
- package/src/index.ts +987 -296
- package/src/kill-switch-cli.ts +113 -0
- package/src/manifests-cli.ts +168 -0
- package/src/preview-runtime.ts +14 -9
- package/src/sandbox-provider.ts +440 -0
- package/src/secret-redaction.ts +124 -0
- package/src/session-timeline.ts +152 -0
- package/src/setup-prompt.ts +4 -2
- package/src/skill-installer.ts +13 -1
- package/src/smoke.ts +342 -0
- package/src/task-assignment-manifest.ts +70 -2
- package/src/vercel-sandbox-provider.ts +388 -0
- package/src/workbench-cli.ts +701 -0
- package/src/workspace-sync.ts +1148 -0
- package/src/workspace.ts +1051 -64
- package/vendor/skills/orizu-cli/SKILL.md +3 -1
- package/vendor/skills/orizu-cli/references/cli-reference.md +3 -3
package/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,15 +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 {
|
|
21
|
-
import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags
|
|
22
|
+
import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js';
|
|
23
|
+
import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags } from './http.js';
|
|
22
24
|
import { computeSkillContentHash, getSkillInstallPath, getSkillTargetStatus, getTargetForAgent, installSkillTarget, isSkillInstallAgent, isSkillInstallTarget, resolveSkillSource, SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
|
|
23
25
|
import { renderBanner } from './banner.js';
|
|
24
26
|
import { formatTaskCreateError } from './task-create-error.js';
|
|
25
27
|
import { renderAgentSetupPrompt } from './setup-prompt.js';
|
|
26
|
-
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
|
+
}
|
|
27
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
|
+
};
|
|
28
59
|
function getCliVersion() {
|
|
29
60
|
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
30
61
|
if (typeof packageJson.version !== 'string' || packageJson.version.length === 0) {
|
|
@@ -49,12 +80,6 @@ function printUsage() {
|
|
|
49
80
|
printBannerIfInteractive();
|
|
50
81
|
printLine(renderRootHelp());
|
|
51
82
|
}
|
|
52
|
-
function printPreviewUsage() {
|
|
53
|
-
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>]`);
|
|
54
|
-
}
|
|
55
|
-
function printOptimizationUsage() {
|
|
56
|
-
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]`);
|
|
57
|
-
}
|
|
58
83
|
let cliArgs = process.argv.slice(2);
|
|
59
84
|
let cliJsonOutput = false;
|
|
60
85
|
function getArg(name) {
|
|
@@ -79,6 +104,17 @@ function getArgs(name) {
|
|
|
79
104
|
}
|
|
80
105
|
return values;
|
|
81
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
|
+
}
|
|
82
118
|
function rejectDashPrefixedOptionValue(name, value) {
|
|
83
119
|
if (value && value.startsWith('-')) {
|
|
84
120
|
throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
|
|
@@ -123,6 +159,9 @@ export function createCodeChallenge(verifier) {
|
|
|
123
159
|
export function sanitizeTerminalText(value) {
|
|
124
160
|
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
125
161
|
}
|
|
162
|
+
function printError(message) {
|
|
163
|
+
console.error(sanitizeTerminalText(message));
|
|
164
|
+
}
|
|
126
165
|
export function validateBrowserUrl(url, expectedOrigin) {
|
|
127
166
|
let parsed;
|
|
128
167
|
try {
|
|
@@ -266,7 +305,7 @@ function readJsonObjectArg(valueArg, label) {
|
|
|
266
305
|
return parsed;
|
|
267
306
|
}
|
|
268
307
|
catch (error) {
|
|
269
|
-
throw new Error(`Invalid ${label} '${valueArg}': ${
|
|
308
|
+
throw new Error(`Invalid ${label} '${valueArg}': ${getErrorMessage(error)}`);
|
|
270
309
|
}
|
|
271
310
|
}
|
|
272
311
|
function readJsonPayloadArg(pathArg) {
|
|
@@ -352,6 +391,38 @@ async function fetchTeamMembers(teamSlug) {
|
|
|
352
391
|
const data = await parseJsonResponse(response, 'Team members list');
|
|
353
392
|
return data.members;
|
|
354
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
|
+
}
|
|
355
426
|
async function resolveProjectSelection(projectArg) {
|
|
356
427
|
projectArg = projectArg || process.env.ORIZU_PROJECT || null;
|
|
357
428
|
const teams = await fetchTeams();
|
|
@@ -371,7 +442,7 @@ async function resolveProjectSelection(projectArg) {
|
|
|
371
442
|
const [teamSlug, projectSlug] = segments.map(normalizeSlugInput);
|
|
372
443
|
const matchedTeam = teams.find(team => team.slug === teamSlug);
|
|
373
444
|
if (!matchedTeam) {
|
|
374
|
-
|
|
445
|
+
printError(`Team '${teamSlug}' not found in your accessible teams.`);
|
|
375
446
|
const selectedTeam = await promptSelect('Select a team', teams, team => `${team.name} (${team.slug})`);
|
|
376
447
|
const projects = await fetchProjects(selectedTeam.slug);
|
|
377
448
|
const selectedProject = await promptSelect(`Select a project in ${selectedTeam.slug}`, projects, project => `${project.name} (${project.teamSlug}/${project.slug})`);
|
|
@@ -380,7 +451,7 @@ async function resolveProjectSelection(projectArg) {
|
|
|
380
451
|
const projects = await fetchProjects(matchedTeam.slug);
|
|
381
452
|
const matchedProject = projects.find(project => project.slug === projectSlug);
|
|
382
453
|
if (!matchedProject) {
|
|
383
|
-
|
|
454
|
+
printError(`Project '${projectSlug}' not found in team '${matchedTeam.slug}'.`);
|
|
384
455
|
const selectedProject = await promptSelect(`Select a project in ${matchedTeam.slug}`, projects, project => `${project.name} (${project.teamSlug}/${project.slug})`);
|
|
385
456
|
return selectedProject;
|
|
386
457
|
}
|
|
@@ -1989,20 +2060,12 @@ async function createTeam() {
|
|
|
1989
2060
|
if (!name) {
|
|
1990
2061
|
throw new Error('Usage: orizu teams create --name <name>');
|
|
1991
2062
|
}
|
|
1992
|
-
const
|
|
1993
|
-
method: 'POST',
|
|
1994
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1995
|
-
body: JSON.stringify({ name }),
|
|
1996
|
-
});
|
|
1997
|
-
if (!response.ok) {
|
|
1998
|
-
throw new Error(`Failed to create team: ${await response.text()}`);
|
|
1999
|
-
}
|
|
2000
|
-
const data = await parseJsonResponse(response, 'Team create');
|
|
2063
|
+
const team = await createTeamOnServer(name);
|
|
2001
2064
|
if (hasJsonFlag()) {
|
|
2002
|
-
printJson({ team
|
|
2065
|
+
printJson({ team });
|
|
2003
2066
|
return;
|
|
2004
2067
|
}
|
|
2005
|
-
printLine(`Created team: ${sanitizeTerminalText(
|
|
2068
|
+
printLine(`Created team: ${sanitizeTerminalText(team.name)} (${sanitizeTerminalText(team.slug)})`);
|
|
2006
2069
|
}
|
|
2007
2070
|
async function listProjects() {
|
|
2008
2071
|
const teamSlugArg = getArg('--team');
|
|
@@ -2025,20 +2088,12 @@ async function createProject() {
|
|
|
2025
2088
|
const team = await promptSelect('Select a team', await fetchTeams(), item => `${item.name} (${item.slug})`, { forcePrompt: true });
|
|
2026
2089
|
teamSlug = team.slug;
|
|
2027
2090
|
}
|
|
2028
|
-
const
|
|
2029
|
-
method: 'POST',
|
|
2030
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2031
|
-
body: JSON.stringify({ teamSlug, name }),
|
|
2032
|
-
});
|
|
2033
|
-
if (!response.ok) {
|
|
2034
|
-
throw new Error(`Failed to create project: ${await response.text()}`);
|
|
2035
|
-
}
|
|
2036
|
-
const data = await parseJsonResponse(response, 'Project create');
|
|
2091
|
+
const project = await createProjectOnServer(teamSlug, name);
|
|
2037
2092
|
if (hasJsonFlag()) {
|
|
2038
|
-
printJson({ project
|
|
2093
|
+
printJson({ project });
|
|
2039
2094
|
return;
|
|
2040
2095
|
}
|
|
2041
|
-
printLine(`Created project ${sanitizeTerminalText(`${
|
|
2096
|
+
printLine(`Created project ${sanitizeTerminalText(`${project.teamSlug}/${project.slug}`)}`);
|
|
2042
2097
|
}
|
|
2043
2098
|
async function listTasks() {
|
|
2044
2099
|
const project = getArg('--project');
|
|
@@ -2064,10 +2119,10 @@ function readSourceFile(pathArg) {
|
|
|
2064
2119
|
return readFileSync(expandedPath, 'utf-8');
|
|
2065
2120
|
}
|
|
2066
2121
|
catch (error) {
|
|
2067
|
-
if (error
|
|
2122
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2068
2123
|
throw new Error(`File not found: ${expandedPath}`);
|
|
2069
2124
|
}
|
|
2070
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2125
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`);
|
|
2071
2126
|
}
|
|
2072
2127
|
}
|
|
2073
2128
|
function readSourceBytes(pathArg) {
|
|
@@ -2076,10 +2131,10 @@ function readSourceBytes(pathArg) {
|
|
|
2076
2131
|
return readFileSync(expandedPath);
|
|
2077
2132
|
}
|
|
2078
2133
|
catch (error) {
|
|
2079
|
-
if (error
|
|
2134
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
2080
2135
|
throw new Error(`File not found: ${expandedPath}`);
|
|
2081
2136
|
}
|
|
2082
|
-
throw new Error(`Failed to read file '${expandedPath}': ${
|
|
2137
|
+
throw new Error(`Failed to read file '${expandedPath}': ${getErrorMessage(error)}`);
|
|
2083
2138
|
}
|
|
2084
2139
|
}
|
|
2085
2140
|
function hasJsonFlag() {
|
|
@@ -2116,12 +2171,261 @@ async function askYesNo(question, defaultYes) {
|
|
|
2116
2171
|
rl.close();
|
|
2117
2172
|
}
|
|
2118
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
|
+
}
|
|
2119
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]`;
|
|
2120
2422
|
function describeSkillTarget(target) {
|
|
2121
2423
|
if (target === 'claude-user')
|
|
2122
2424
|
return 'Claude Code, for you across all projects';
|
|
2123
2425
|
if (target === 'claude-project')
|
|
2124
2426
|
return 'Claude Code, only for this project';
|
|
2427
|
+
if (target === 'codex-user')
|
|
2428
|
+
return 'Codex, for you across all projects';
|
|
2125
2429
|
if (target === 'agent-user')
|
|
2126
2430
|
return 'Codex / Open Agent Skills, for you across all projects';
|
|
2127
2431
|
if (target === 'agents-project')
|
|
@@ -2223,8 +2527,16 @@ function describePlannedWrite(target, action, mode, path) {
|
|
|
2223
2527
|
async function applySkillInstallTargets(targets, options) {
|
|
2224
2528
|
const cliVersion = getCliVersion();
|
|
2225
2529
|
const jsonMode = hasJsonFlag();
|
|
2226
|
-
const planned = targets.map(target => installSkillTarget(target, {
|
|
2227
|
-
|
|
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) {
|
|
2228
2540
|
printLine('Planned writes:');
|
|
2229
2541
|
for (const plan of planned) {
|
|
2230
2542
|
printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path));
|
|
@@ -2232,7 +2544,7 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2232
2544
|
printLine('');
|
|
2233
2545
|
}
|
|
2234
2546
|
if (options.dryRun) {
|
|
2235
|
-
if (!jsonMode) {
|
|
2547
|
+
if (printResults && !jsonMode) {
|
|
2236
2548
|
printLine('Dry run: no files were changed.');
|
|
2237
2549
|
}
|
|
2238
2550
|
return planned.map(plan => ({
|
|
@@ -2245,8 +2557,8 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2245
2557
|
const outcomes = [];
|
|
2246
2558
|
for (const target of targets) {
|
|
2247
2559
|
let overwrite = options.skipConfirm;
|
|
2248
|
-
const installPath = getSkillInstallPath(target);
|
|
2249
|
-
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 })) {
|
|
2250
2562
|
if (!isInteractiveTerminal()) {
|
|
2251
2563
|
throw new Error(`${installPath} already exists. Pass --yes to replace it.`);
|
|
2252
2564
|
}
|
|
@@ -2261,11 +2573,13 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2261
2573
|
}
|
|
2262
2574
|
try {
|
|
2263
2575
|
const result = installSkillTarget(target, {
|
|
2576
|
+
cwd: options.cwd,
|
|
2577
|
+
homeDir: options.homeDir,
|
|
2264
2578
|
overwrite,
|
|
2265
2579
|
mode: options.mode,
|
|
2266
2580
|
cliVersion,
|
|
2267
2581
|
});
|
|
2268
|
-
if (!jsonMode) {
|
|
2582
|
+
if (printResults && !jsonMode) {
|
|
2269
2583
|
const suffix = result.mode === 'link' ? ' (symlink)' : '';
|
|
2270
2584
|
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`);
|
|
2271
2585
|
}
|
|
@@ -2277,8 +2591,8 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
2277
2591
|
});
|
|
2278
2592
|
}
|
|
2279
2593
|
catch (error) {
|
|
2280
|
-
const message =
|
|
2281
|
-
if (!jsonMode) {
|
|
2594
|
+
const message = getErrorMessage(error);
|
|
2595
|
+
if (printResults && !jsonMode) {
|
|
2282
2596
|
printLine(`Failed ${installPath}: ${message}`);
|
|
2283
2597
|
}
|
|
2284
2598
|
outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message });
|
|
@@ -2440,15 +2754,124 @@ function describeSetupAuthState() {
|
|
|
2440
2754
|
baseUrl,
|
|
2441
2755
|
};
|
|
2442
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
|
+
}
|
|
2443
2856
|
async function setupCommand() {
|
|
2444
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'));
|
|
2445
2862
|
const skipConfirm = hasArg('--yes');
|
|
2446
|
-
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';
|
|
2447
2870
|
printBannerIfInteractive();
|
|
2448
2871
|
printLine('Orizu setup');
|
|
2449
2872
|
printLine('');
|
|
2450
|
-
//
|
|
2451
|
-
printLine('1
|
|
2873
|
+
// Step 1: login
|
|
2874
|
+
printLine('Step 1: Login');
|
|
2452
2875
|
let auth = describeSetupAuthState();
|
|
2453
2876
|
if (hasArg('--skip-login')) {
|
|
2454
2877
|
printLine(' Skipped (--skip-login).');
|
|
@@ -2459,72 +2882,177 @@ async function setupCommand() {
|
|
|
2459
2882
|
else if (noInput || dryRun) {
|
|
2460
2883
|
printLine(' Not signed in. Run `orizu login` to authenticate.');
|
|
2461
2884
|
}
|
|
2462
|
-
else if (await
|
|
2885
|
+
else if (await waitForAnyKeyOrEscape(' Press any key to open a browser and connect to your account, or Esc to cancel.')) {
|
|
2463
2886
|
await login();
|
|
2464
2887
|
auth = describeSetupAuthState();
|
|
2465
2888
|
}
|
|
2466
2889
|
else {
|
|
2467
|
-
|
|
2890
|
+
throw new Error('Setup cancelled.');
|
|
2468
2891
|
}
|
|
2469
2892
|
printLine('');
|
|
2470
|
-
//
|
|
2471
|
-
printLine('2
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
let
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
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
|
+
}
|
|
2478
2992
|
}
|
|
2479
2993
|
else {
|
|
2480
|
-
|
|
2994
|
+
printLine(` Skipped. Rerun with --workspace to initialize the workspace contract.`);
|
|
2481
2995
|
}
|
|
2482
2996
|
}
|
|
2483
|
-
let installOutcomes = [];
|
|
2484
|
-
if (targets.length > 0) {
|
|
2485
|
-
installOutcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
|
|
2486
|
-
}
|
|
2487
|
-
else if (hasArg('--no-install')) {
|
|
2488
|
-
printLine(' Skipped (--no-install).');
|
|
2489
|
-
}
|
|
2490
2997
|
printLine('');
|
|
2491
|
-
//
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
if (hasArg('--no-
|
|
2495
|
-
printLine(' Skipped (--no-
|
|
2496
|
-
}
|
|
2497
|
-
else if (workspaceExists()) {
|
|
2498
|
-
workspaceState = 'exists';
|
|
2499
|
-
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.');
|
|
2500
3003
|
}
|
|
2501
3004
|
else {
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
for (const action of result.actions) {
|
|
2512
|
-
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);
|
|
3011
|
+
}
|
|
3012
|
+
else {
|
|
3013
|
+
printLine(' No Codex or Claude Code agent folders were detected.');
|
|
2513
3014
|
}
|
|
2514
3015
|
}
|
|
3016
|
+
if (targets.length === 0) {
|
|
3017
|
+
printLine(' Skipped skill install.');
|
|
3018
|
+
}
|
|
2515
3019
|
else {
|
|
2516
|
-
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
|
+
}
|
|
2517
3045
|
}
|
|
2518
3046
|
}
|
|
2519
3047
|
printLine('');
|
|
2520
3048
|
// Phase 4: coding-agent handoff
|
|
2521
3049
|
const handoffPrompt = renderAgentSetupPrompt({
|
|
2522
|
-
workspacePath:
|
|
2523
|
-
? getWorkspaceRoot()
|
|
3050
|
+
workspacePath: workspaceResult && workspaceState !== 'invalid'
|
|
3051
|
+
? getWorkspaceRoot(workspaceRoot)
|
|
2524
3052
|
: null,
|
|
2525
3053
|
});
|
|
2526
|
-
if (!hasArg('--no-handoff')) {
|
|
2527
|
-
printLine('4
|
|
3054
|
+
if (handoffRequested && !hasArg('--no-handoff')) {
|
|
3055
|
+
printLine('Step 4: Coding-agent handoff');
|
|
2528
3056
|
printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:');
|
|
2529
3057
|
printLine('');
|
|
2530
3058
|
printLine('--- prompt start ---');
|
|
@@ -2564,7 +3092,7 @@ async function setupCommand() {
|
|
|
2564
3092
|
const summaryTargets = installOutcomes.length > 0
|
|
2565
3093
|
? installOutcomes
|
|
2566
3094
|
.filter(outcome => outcome.action !== 'failed')
|
|
2567
|
-
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
|
|
3095
|
+
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target, { cwd: workspaceRoot, homeDir: setupSkillHome }) }))
|
|
2568
3096
|
: [];
|
|
2569
3097
|
const source = resolveSkillSource();
|
|
2570
3098
|
if (hasJsonFlag()) {
|
|
@@ -2583,29 +3111,45 @@ async function setupCommand() {
|
|
|
2583
3111
|
cliVersion: getCliVersion(),
|
|
2584
3112
|
skillHash: computeSkillContentHash(source.root),
|
|
2585
3113
|
},
|
|
2586
|
-
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
|
+
},
|
|
2587
3129
|
});
|
|
2588
3130
|
return;
|
|
2589
3131
|
}
|
|
2590
3132
|
printLine('Setup summary');
|
|
2591
3133
|
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`);
|
|
2592
3134
|
if (summaryTargets.length > 0) {
|
|
2593
|
-
printLine('
|
|
3135
|
+
printLine(' Skills:');
|
|
2594
3136
|
for (const { outcome, status } of summaryTargets) {
|
|
2595
3137
|
const modeLabel = status.mode ? `, ${status.mode}` : '';
|
|
2596
3138
|
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`);
|
|
2597
3139
|
}
|
|
2598
3140
|
}
|
|
2599
3141
|
else {
|
|
2600
|
-
printLine('
|
|
3142
|
+
printLine(' Skills: none installed this run');
|
|
2601
3143
|
}
|
|
2602
3144
|
const failed = installOutcomes.filter(outcome => outcome.action === 'failed');
|
|
2603
3145
|
for (const failure of failed) {
|
|
2604
3146
|
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`);
|
|
2605
3147
|
}
|
|
2606
|
-
printLine(`
|
|
2607
|
-
|
|
2608
|
-
|
|
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.');
|
|
2609
3153
|
}
|
|
2610
3154
|
function setupPromptCommand() {
|
|
2611
3155
|
const prompt = renderAgentSetupPrompt({
|
|
@@ -2627,7 +3171,7 @@ function readJsonFile(pathArg) {
|
|
|
2627
3171
|
return parsed;
|
|
2628
3172
|
}
|
|
2629
3173
|
catch (error) {
|
|
2630
|
-
throw new Error(`Invalid JSON file '${pathArg}': ${
|
|
3174
|
+
throw new Error(`Invalid JSON file '${pathArg}': ${getErrorMessage(error)}`);
|
|
2631
3175
|
}
|
|
2632
3176
|
}
|
|
2633
3177
|
function isRecord(value) {
|
|
@@ -2691,126 +3235,6 @@ function writeTextFileEnsuringDir(pathArg, content) {
|
|
|
2691
3235
|
mkdirSync(dirname(pathArg), { recursive: true });
|
|
2692
3236
|
writeFileSync(pathArg, content);
|
|
2693
3237
|
}
|
|
2694
|
-
function shouldExcludeArtifactPath(relativePath) {
|
|
2695
|
-
const parts = relativePath.split('/');
|
|
2696
|
-
return parts.some(part => part === '.git' ||
|
|
2697
|
-
part === '.DS_Store' ||
|
|
2698
|
-
part === '__pycache__' ||
|
|
2699
|
-
part === '.pytest_cache');
|
|
2700
|
-
}
|
|
2701
|
-
function collectArtifactFiles(sourceDir, relativeDir = '') {
|
|
2702
|
-
const absoluteDir = relativeDir ? join(sourceDir, relativeDir) : sourceDir;
|
|
2703
|
-
return readdirSync(absoluteDir, { withFileTypes: true })
|
|
2704
|
-
.flatMap(entry => {
|
|
2705
|
-
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
2706
|
-
if (shouldExcludeArtifactPath(relativePath)) {
|
|
2707
|
-
return [];
|
|
2708
|
-
}
|
|
2709
|
-
if (entry.isDirectory()) {
|
|
2710
|
-
return collectArtifactFiles(sourceDir, relativePath);
|
|
2711
|
-
}
|
|
2712
|
-
return entry.isFile() ? [relativePath] : [];
|
|
2713
|
-
})
|
|
2714
|
-
.sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
2715
|
-
}
|
|
2716
|
-
const ZIP_GENERAL_PURPOSE_UTF8 = 0x0800;
|
|
2717
|
-
const ZIP_DOS_TIME_2000_01_01 = 0;
|
|
2718
|
-
const ZIP_DOS_DATE_2000_01_01 = ((2000 - 1980) << 9) | (1 << 5) | 1;
|
|
2719
|
-
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
2720
|
-
let value = index;
|
|
2721
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
2722
|
-
value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1);
|
|
2723
|
-
}
|
|
2724
|
-
return value >>> 0;
|
|
2725
|
-
});
|
|
2726
|
-
function crc32(bytes) {
|
|
2727
|
-
let crc = 0xffffffff;
|
|
2728
|
-
for (const byte of bytes) {
|
|
2729
|
-
crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
2730
|
-
}
|
|
2731
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
2732
|
-
}
|
|
2733
|
-
function createStoredZip(entries) {
|
|
2734
|
-
const localParts = [];
|
|
2735
|
-
const centralParts = [];
|
|
2736
|
-
let offset = 0;
|
|
2737
|
-
for (const entry of entries) {
|
|
2738
|
-
const name = Buffer.from(entry.path, 'utf8');
|
|
2739
|
-
const crc = crc32(entry.data);
|
|
2740
|
-
const localHeader = Buffer.alloc(30);
|
|
2741
|
-
localHeader.writeUInt32LE(0x04034b50, 0);
|
|
2742
|
-
localHeader.writeUInt16LE(10, 4);
|
|
2743
|
-
localHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 6);
|
|
2744
|
-
localHeader.writeUInt16LE(0, 8);
|
|
2745
|
-
localHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 10);
|
|
2746
|
-
localHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 12);
|
|
2747
|
-
localHeader.writeUInt32LE(crc, 14);
|
|
2748
|
-
localHeader.writeUInt32LE(entry.data.length, 18);
|
|
2749
|
-
localHeader.writeUInt32LE(entry.data.length, 22);
|
|
2750
|
-
localHeader.writeUInt16LE(name.length, 26);
|
|
2751
|
-
localHeader.writeUInt16LE(0, 28);
|
|
2752
|
-
localParts.push(localHeader, name, entry.data);
|
|
2753
|
-
const centralHeader = Buffer.alloc(46);
|
|
2754
|
-
centralHeader.writeUInt32LE(0x02014b50, 0);
|
|
2755
|
-
centralHeader.writeUInt16LE(20, 4);
|
|
2756
|
-
centralHeader.writeUInt16LE(10, 6);
|
|
2757
|
-
centralHeader.writeUInt16LE(ZIP_GENERAL_PURPOSE_UTF8, 8);
|
|
2758
|
-
centralHeader.writeUInt16LE(0, 10);
|
|
2759
|
-
centralHeader.writeUInt16LE(ZIP_DOS_TIME_2000_01_01, 12);
|
|
2760
|
-
centralHeader.writeUInt16LE(ZIP_DOS_DATE_2000_01_01, 14);
|
|
2761
|
-
centralHeader.writeUInt32LE(crc, 16);
|
|
2762
|
-
centralHeader.writeUInt32LE(entry.data.length, 20);
|
|
2763
|
-
centralHeader.writeUInt32LE(entry.data.length, 24);
|
|
2764
|
-
centralHeader.writeUInt16LE(name.length, 28);
|
|
2765
|
-
centralHeader.writeUInt16LE(0, 30);
|
|
2766
|
-
centralHeader.writeUInt16LE(0, 32);
|
|
2767
|
-
centralHeader.writeUInt16LE(0, 34);
|
|
2768
|
-
centralHeader.writeUInt16LE(0, 36);
|
|
2769
|
-
centralHeader.writeUInt32LE(0, 38);
|
|
2770
|
-
centralHeader.writeUInt32LE(offset, 42);
|
|
2771
|
-
centralParts.push(centralHeader, name);
|
|
2772
|
-
offset += localHeader.length + name.length + entry.data.length;
|
|
2773
|
-
}
|
|
2774
|
-
const centralDirectoryOffset = offset;
|
|
2775
|
-
const centralDirectory = Buffer.concat(centralParts);
|
|
2776
|
-
const endOfCentralDirectory = Buffer.alloc(22);
|
|
2777
|
-
endOfCentralDirectory.writeUInt32LE(0x06054b50, 0);
|
|
2778
|
-
endOfCentralDirectory.writeUInt16LE(0, 4);
|
|
2779
|
-
endOfCentralDirectory.writeUInt16LE(0, 6);
|
|
2780
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 8);
|
|
2781
|
-
endOfCentralDirectory.writeUInt16LE(entries.length, 10);
|
|
2782
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12);
|
|
2783
|
-
endOfCentralDirectory.writeUInt32LE(centralDirectoryOffset, 16);
|
|
2784
|
-
endOfCentralDirectory.writeUInt16LE(0, 20);
|
|
2785
|
-
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory]);
|
|
2786
|
-
}
|
|
2787
|
-
function zipDirectoryToBase64(dirArg) {
|
|
2788
|
-
const sourceDir = expandHomePath(dirArg);
|
|
2789
|
-
try {
|
|
2790
|
-
const stats = statSync(sourceDir);
|
|
2791
|
-
if (!stats.isDirectory()) {
|
|
2792
|
-
throw new Error(`${sourceDir} is not a directory`);
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
catch (error) {
|
|
2796
|
-
if (error?.code === 'ENOENT') {
|
|
2797
|
-
throw new Error(`Directory not found: ${sourceDir}`);
|
|
2798
|
-
}
|
|
2799
|
-
throw error;
|
|
2800
|
-
}
|
|
2801
|
-
const files = collectArtifactFiles(sourceDir);
|
|
2802
|
-
if (files.length === 0) {
|
|
2803
|
-
throw new Error(`Directory contains no artifact files: ${sourceDir}`);
|
|
2804
|
-
}
|
|
2805
|
-
const bytes = createStoredZip(files.map(relativePath => ({
|
|
2806
|
-
path: relativePath,
|
|
2807
|
-
data: readFileSync(join(sourceDir, relativePath)),
|
|
2808
|
-
})));
|
|
2809
|
-
return {
|
|
2810
|
-
zipBase64: bytes.toString('base64'),
|
|
2811
|
-
contentSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
2812
|
-
};
|
|
2813
|
-
}
|
|
2814
3238
|
function readManifestFile(dirArg) {
|
|
2815
3239
|
return readJsonFile(join(expandHomePath(dirArg), 'manifest.json'));
|
|
2816
3240
|
}
|
|
@@ -2991,8 +3415,32 @@ function readAssignmentManifest(path) {
|
|
|
2991
3415
|
if (!path) {
|
|
2992
3416
|
return null;
|
|
2993
3417
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
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;
|
|
2996
3444
|
}
|
|
2997
3445
|
async function createTask() {
|
|
2998
3446
|
const projectSlug = getArg('--project');
|
|
@@ -3103,6 +3551,127 @@ async function createTask() {
|
|
|
3103
3551
|
: `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`}`
|
|
3104
3552
|
: ''));
|
|
3105
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
|
+
}
|
|
3106
3675
|
async function publishTask() {
|
|
3107
3676
|
const taskId = getArg('--task');
|
|
3108
3677
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
@@ -3241,9 +3810,9 @@ async function taskStatus() {
|
|
|
3241
3810
|
}
|
|
3242
3811
|
async function updateTaskStatus(targetStatus) {
|
|
3243
3812
|
const taskId = getArg('--task');
|
|
3813
|
+
const labels = TASK_STATUS_ACTION_LABELS[targetStatus];
|
|
3244
3814
|
if (!taskId) {
|
|
3245
|
-
|
|
3246
|
-
throw new Error(`Usage: orizu tasks ${verb} --task <taskId>`);
|
|
3815
|
+
throw new Error(`Usage: orizu tasks ${labels.verb} --task <taskId>`);
|
|
3247
3816
|
}
|
|
3248
3817
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`, {
|
|
3249
3818
|
method: 'PATCH',
|
|
@@ -3251,16 +3820,14 @@ async function updateTaskStatus(targetStatus) {
|
|
|
3251
3820
|
body: JSON.stringify({ status: targetStatus }),
|
|
3252
3821
|
});
|
|
3253
3822
|
if (!response.ok) {
|
|
3254
|
-
|
|
3255
|
-
throw new Error(`Failed to ${verb} task: ${await response.text()}`);
|
|
3823
|
+
throw new Error(`Failed to ${labels.verb} task: ${await response.text()}`);
|
|
3256
3824
|
}
|
|
3257
3825
|
const data = await parseJsonResponse(response, 'Task status update');
|
|
3258
3826
|
if (hasJsonFlag()) {
|
|
3259
3827
|
printJson({ task: data.task });
|
|
3260
3828
|
return;
|
|
3261
3829
|
}
|
|
3262
|
-
|
|
3263
|
-
printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
|
|
3830
|
+
printLine(`${labels.pastTense} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
|
|
3264
3831
|
}
|
|
3265
3832
|
async function setTaskReport() {
|
|
3266
3833
|
const taskId = getArg('--task');
|
|
@@ -3436,10 +4003,10 @@ async function changeTeamMemberRole() {
|
|
|
3436
4003
|
const email = getArg('--email');
|
|
3437
4004
|
const role = getArg('--role');
|
|
3438
4005
|
if (!teamSlug || !email || !role) {
|
|
3439
|
-
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>');
|
|
3440
4007
|
}
|
|
3441
|
-
if (!['admin', '
|
|
3442
|
-
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');
|
|
3443
4010
|
}
|
|
3444
4011
|
const members = await fetchTeamMembers(teamSlug);
|
|
3445
4012
|
const member = members.find(item => item.email.toLowerCase() === email.toLowerCase());
|
|
@@ -4198,12 +4765,16 @@ async function runnersExec() {
|
|
|
4198
4765
|
throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(boundedRunnerOutput(result.stderr || result.stdout))}`);
|
|
4199
4766
|
}
|
|
4200
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.
|
|
4201
4770
|
resultLines.push(JSON.stringify({
|
|
4771
|
+
...runnerOutput,
|
|
4202
4772
|
row_id: row.id,
|
|
4203
4773
|
prompt_version_id: context.prompt.promptVersionId,
|
|
4204
4774
|
runner_version_id: context.prompt.runnerVersionId,
|
|
4205
|
-
...(scorerVersion
|
|
4206
|
-
|
|
4775
|
+
...(scorerVersion
|
|
4776
|
+
? { scorer_version_id: context.scorer?.versionId || scorerVersion, metric_key: context.scorer?.metricKey || 'score' }
|
|
4777
|
+
: {}),
|
|
4207
4778
|
}));
|
|
4208
4779
|
}
|
|
4209
4780
|
finally {
|
|
@@ -4503,6 +5074,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4503
5074
|
await createTask();
|
|
4504
5075
|
return;
|
|
4505
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
|
+
}
|
|
4506
5085
|
if (command === 'tasks' && subcommand === 'publish') {
|
|
4507
5086
|
await publishTask();
|
|
4508
5087
|
return;
|
|
@@ -4529,6 +5108,10 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4529
5108
|
await updateTaskStatus('active');
|
|
4530
5109
|
return;
|
|
4531
5110
|
}
|
|
5111
|
+
if (command === 'tasks' && subcommand === 'complete') {
|
|
5112
|
+
await updateTaskStatus('completed');
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
4532
5115
|
if (command === 'datasets' && subcommand === 'upload') {
|
|
4533
5116
|
await uploadDataset();
|
|
4534
5117
|
return;
|
|
@@ -4582,6 +5165,41 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
4582
5165
|
await downloadAnnotations();
|
|
4583
5166
|
return;
|
|
4584
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
|
+
}
|
|
4585
5203
|
printUsage();
|
|
4586
5204
|
process.exit(1);
|
|
4587
5205
|
}
|
|
@@ -4599,7 +5217,7 @@ function isCliEntrypoint() {
|
|
|
4599
5217
|
}
|
|
4600
5218
|
if (isCliEntrypoint()) {
|
|
4601
5219
|
main().catch(error => {
|
|
4602
|
-
|
|
5220
|
+
printError(error instanceof Error ? error.message : 'Unknown error');
|
|
4603
5221
|
process.exit(1);
|
|
4604
5222
|
});
|
|
4605
5223
|
}
|