orizu 0.5.24 → 0.5.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +279 -59
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +5 -2
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +239 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -213
- package/dist/keyboard-select.d.ts +7 -0
- package/dist/keyboard-select.d.ts.map +1 -0
- package/dist/keyboard-select.js +72 -0
- package/dist/logout-diagnostic.d.ts +4 -0
- package/dist/logout-diagnostic.d.ts.map +1 -0
- package/dist/logout-diagnostic.js +26 -0
- package/dist/setup-account.d.ts +11 -0
- package/dist/setup-account.d.ts.map +1 -0
- package/dist/setup-account.js +106 -0
- package/dist/setup-onboarding.d.ts +27 -1
- package/dist/setup-onboarding.d.ts.map +1 -1
- package/dist/setup-onboarding.js +95 -11
- package/dist/setup-summary.d.ts +12 -0
- package/dist/setup-summary.d.ts.map +1 -0
- package/dist/setup-summary.js +56 -0
- package/package.json +1 -1
- package/src/credentials.ts +326 -57
- package/src/help.ts +7 -2
- package/src/http.ts +325 -20
- package/src/index.ts +193 -279
- package/src/keyboard-select.ts +97 -0
- package/src/logout-diagnostic.ts +39 -0
- package/src/setup-account.ts +129 -0
- package/src/setup-onboarding.ts +133 -10
- package/src/setup-summary.ts +77 -0
- package/vendor/skills/orizu/SKILL.md +51 -111
- package/vendor/skills/orizu/flows/expand.md +27 -0
- package/vendor/skills/orizu/flows/first-win.md +31 -0
- package/vendor/skills/orizu/flows/onboard.md +31 -0
- package/vendor/skills/orizu/flows/promote.md +33 -0
- package/vendor/skills/orizu/flows/recurse.md +23 -0
- package/vendor/skills/orizu/flows/triage.md +37 -0
- package/vendor/skills/orizu/references/assess-and-plan.md +5 -5
- package/vendor/skills/orizu/references/authority-map.md +20 -0
- package/vendor/skills/orizu/references/building-apps.md +7 -1
- package/vendor/skills/orizu/references/building-judges.md +15 -16
- package/vendor/skills/orizu/references/cli-reference.md +17 -8
- package/vendor/skills/orizu/references/dataset-design.md +16 -6
- package/vendor/skills/orizu/references/eval-strategy.md +18 -15
- package/vendor/skills/orizu/references/instructions-after-prompts.md +2 -2
- package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +10 -6
- package/vendor/skills/orizu/references/optimization-reports.md +11 -11
- package/vendor/skills/orizu/references/optimization-with-gepa.md +4 -2
- package/vendor/skills/orizu/references/primer.md +18 -22
- package/vendor/skills/orizu/references/prompt-control-plane.md +5 -5
- package/vendor/skills/orizu/references/vocabulary.md +27 -0
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, rel
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { createServer } from 'http';
|
|
6
6
|
import { gunzipSync, gzipSync } from 'zlib';
|
|
7
|
-
import {
|
|
7
|
+
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from 'fs';
|
|
8
8
|
import { spawn, spawnSync } from 'child_process';
|
|
9
|
-
import {
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
10
|
import { createInterface } from 'readline/promises';
|
|
11
11
|
import { emitKeypressEvents } from 'readline';
|
|
12
12
|
import { stdin as input, stdout as output } from 'process';
|
|
@@ -18,17 +18,21 @@ import { parseDatasetReference } from './dataset-download.js';
|
|
|
18
18
|
import { editDatasetRows as runEditDatasetRows } from './dataset-edit-rows.js';
|
|
19
19
|
import { ensureDatasetUploadSnapshot } from './dataset-upload-snapshot.js';
|
|
20
20
|
import { extractErrorMessage } from './error-response.js';
|
|
21
|
+
import { describeLogoutHttpFailure, describeLogoutTransportFailure, } from './logout-diagnostic.js';
|
|
21
22
|
import { parseGlobalFlags } from './global-flags.js';
|
|
22
23
|
import { shouldUseHeadlessLogin, waitForHeadlessAuthorization } from './headless-login.js';
|
|
23
24
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js';
|
|
24
25
|
import { runLocalAppPreview } from './preview-runtime.js';
|
|
25
26
|
import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js';
|
|
26
|
-
import { assertSecureTokenTransport, authedFetch, credentialRequestRedirectPolicy, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
|
|
27
|
+
import { assertSecureTokenTransport, authedFetch, captureAuthenticatedRequestContext, credentialRequestRedirectPolicy, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
|
|
27
28
|
import { computeSkillContentHash, getSkillInstallPath, getSkillTargetStatus, getTargetForAgent, installSkillTarget, isSkillInstallAgent, isSkillInstallTarget, resolveSkillSource, SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
|
|
28
29
|
import { renderBanner } from './banner.js';
|
|
29
30
|
import { formatTaskCreateError } from './task-create-error.js';
|
|
30
31
|
import { renderAgentSetupPrompt } from './setup-prompt.js';
|
|
31
|
-
import { findExecutable,
|
|
32
|
+
import { AGENT_LAUNCH_SPECS, detectedSetupSkillChoices, findExecutable, resolveSetupSkillHome, setupLaunchPrompt, setupNativeTargetForAgent, validateSetupAgentArgs, validateSetupFlagValues, validateSetupSelectionArgs, } from './setup-onboarding.js';
|
|
33
|
+
import { promptKeyboardSelect } from './keyboard-select.js';
|
|
34
|
+
import { confirmExistingSetupAccount, describeSetupAuthState } from './setup-account.js';
|
|
35
|
+
import { renderSetupSkillSummary, sanitizeSetupHumanInlineText } from './setup-summary.js';
|
|
32
36
|
import { getWorkspaceRoot, inspectExistingWorkspaceTeam, initOrizuWorkspace, workspaceExists, } from './workspace.js';
|
|
33
37
|
import { connectorsCommand } from './connectors-cli.js';
|
|
34
38
|
import { modelConfigsCommand } from './model-configs-cli.js';
|
|
@@ -332,18 +336,18 @@ async function promptSelect(title, items, label, options) {
|
|
|
332
336
|
rl.close();
|
|
333
337
|
}
|
|
334
338
|
}
|
|
335
|
-
async function fetchTeams() {
|
|
336
|
-
const response = await
|
|
339
|
+
async function fetchTeams(request = authedFetch) {
|
|
340
|
+
const response = await request('/api/cli/teams');
|
|
337
341
|
if (!response.ok) {
|
|
338
342
|
throw new Error(`Failed to fetch teams: ${await response.text()}`);
|
|
339
343
|
}
|
|
340
344
|
const data = await parseJsonResponse(response, 'Teams list');
|
|
341
345
|
return data.teams;
|
|
342
346
|
}
|
|
343
|
-
async function fetchProjects(teamSlug) {
|
|
347
|
+
async function fetchProjects(teamSlug, request = authedFetch) {
|
|
344
348
|
const normalizedTeamSlug = teamSlug ? normalizeSlugInput(teamSlug) : undefined;
|
|
345
349
|
const query = normalizedTeamSlug ? `?teamSlug=${encodeURIComponent(normalizedTeamSlug)}` : '';
|
|
346
|
-
const response = await
|
|
350
|
+
const response = await request(`/api/cli/projects${query}`);
|
|
347
351
|
if (!response.ok) {
|
|
348
352
|
throw new Error(`Failed to fetch projects: ${await response.text()}`);
|
|
349
353
|
}
|
|
@@ -394,8 +398,8 @@ async function fetchTeamMembers(teamSlug) {
|
|
|
394
398
|
const data = await parseJsonResponse(response, 'Team members list');
|
|
395
399
|
return data.members;
|
|
396
400
|
}
|
|
397
|
-
async function createTeamOnServer(name) {
|
|
398
|
-
const response = await
|
|
401
|
+
async function createTeamOnServer(name, request = authedFetch) {
|
|
402
|
+
const response = await request('/api/cli/teams', {
|
|
399
403
|
method: 'POST',
|
|
400
404
|
headers: { 'Content-Type': 'application/json' },
|
|
401
405
|
body: JSON.stringify({ name }),
|
|
@@ -406,8 +410,8 @@ async function createTeamOnServer(name) {
|
|
|
406
410
|
const data = await parseJsonResponse(response, 'Team create');
|
|
407
411
|
return data.team;
|
|
408
412
|
}
|
|
409
|
-
async function createProjectOnServer(teamSlug, name) {
|
|
410
|
-
const response = await
|
|
413
|
+
async function createProjectOnServer(teamSlug, name, request = authedFetch) {
|
|
414
|
+
const response = await request('/api/cli/projects', {
|
|
411
415
|
method: 'POST',
|
|
412
416
|
headers: { 'Content-Type': 'application/json' },
|
|
413
417
|
body: JSON.stringify({ teamSlug, name }),
|
|
@@ -582,8 +586,9 @@ function resolveAuthCallbackPort() {
|
|
|
582
586
|
}
|
|
583
587
|
return parsed;
|
|
584
588
|
}
|
|
585
|
-
async function login() {
|
|
586
|
-
const baseUrl =
|
|
589
|
+
async function login(baseUrlOverride) {
|
|
590
|
+
const baseUrl = baseUrlOverride
|
|
591
|
+
?? (hasArg('--no-prompt-if-logged-in') ? getBaseUrl() : resolveLoginBaseUrl());
|
|
587
592
|
assertSecureTokenTransport(baseUrl);
|
|
588
593
|
// hasResolvableAuth (ALI-1090): an env bearer (ORIZU_TOKEN / ORIZU_TOKEN_FILE)
|
|
589
594
|
// counts as logged in — a pre-authenticated hosted sandbox must not fall into
|
|
@@ -701,8 +706,8 @@ async function whoami() {
|
|
|
701
706
|
}
|
|
702
707
|
printLine(sanitizeTerminalText(data.user.email ?? data.user.id));
|
|
703
708
|
}
|
|
704
|
-
async function logout() {
|
|
705
|
-
const baseUrl = getBaseUrl();
|
|
709
|
+
async function logout(baseUrlOverride) {
|
|
710
|
+
const baseUrl = baseUrlOverride || getBaseUrl();
|
|
706
711
|
const credentials = getServerCredentials(baseUrl);
|
|
707
712
|
if (!credentials) {
|
|
708
713
|
if (hasJsonFlag()) {
|
|
@@ -712,6 +717,13 @@ async function logout() {
|
|
|
712
717
|
printLine(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`);
|
|
713
718
|
return;
|
|
714
719
|
}
|
|
720
|
+
const authorizationToken = 'accessToken' in credentials
|
|
721
|
+
? credentials.accessToken
|
|
722
|
+
: credentials.apiKey;
|
|
723
|
+
const logoutSecrets = [
|
|
724
|
+
authorizationToken,
|
|
725
|
+
...('refreshToken' in credentials ? [credentials.refreshToken] : []),
|
|
726
|
+
];
|
|
715
727
|
let remoteLogoutError = null;
|
|
716
728
|
try {
|
|
717
729
|
assertSecureTokenTransport(baseUrl);
|
|
@@ -719,7 +731,7 @@ async function logout() {
|
|
|
719
731
|
method: 'POST',
|
|
720
732
|
headers: {
|
|
721
733
|
'Content-Type': 'application/json',
|
|
722
|
-
Authorization: `Bearer ${
|
|
734
|
+
Authorization: `Bearer ${authorizationToken}`,
|
|
723
735
|
},
|
|
724
736
|
body: 'refreshToken' in credentials
|
|
725
737
|
? JSON.stringify({ refreshToken: credentials.refreshToken })
|
|
@@ -727,12 +739,11 @@ async function logout() {
|
|
|
727
739
|
...credentialRequestRedirectPolicy(),
|
|
728
740
|
});
|
|
729
741
|
if (!response.ok) {
|
|
730
|
-
remoteLogoutError =
|
|
742
|
+
remoteLogoutError = await describeLogoutHttpFailure(response, logoutSecrets);
|
|
731
743
|
}
|
|
732
744
|
}
|
|
733
745
|
catch (error) {
|
|
734
|
-
|
|
735
|
-
remoteLogoutError = sanitizeTerminalText(message).slice(0, 180);
|
|
746
|
+
remoteLogoutError = describeLogoutTransportFailure(error, logoutSecrets);
|
|
736
747
|
}
|
|
737
748
|
clearServerCredentials(baseUrl);
|
|
738
749
|
if (hasJsonFlag()) {
|
|
@@ -1965,75 +1976,6 @@ async function waitForAnyKeyOrEscape(message) {
|
|
|
1965
1976
|
rawInput.setRawMode?.(true);
|
|
1966
1977
|
});
|
|
1967
1978
|
}
|
|
1968
|
-
async function promptKeyboardSelect(title, items, label) {
|
|
1969
|
-
if (items.length === 0) {
|
|
1970
|
-
throw new Error(`No options available for ${title.toLowerCase()}`);
|
|
1971
|
-
}
|
|
1972
|
-
if (!isInteractiveTerminal()) {
|
|
1973
|
-
throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`);
|
|
1974
|
-
}
|
|
1975
|
-
printLine(`\n${sanitizeTerminalText(title)}`);
|
|
1976
|
-
printLine('Use ↑/↓ to choose, Enter to confirm, or Esc to cancel.');
|
|
1977
|
-
let selected = 0;
|
|
1978
|
-
let rendered = false;
|
|
1979
|
-
const rawInput = input;
|
|
1980
|
-
const wasRaw = Boolean(rawInput.isRaw);
|
|
1981
|
-
emitKeypressEvents(input);
|
|
1982
|
-
function render() {
|
|
1983
|
-
if (rendered) {
|
|
1984
|
-
output.write(`\x1b[${items.length}A`);
|
|
1985
|
-
}
|
|
1986
|
-
items.forEach((item, index) => {
|
|
1987
|
-
output.write('\x1b[2K');
|
|
1988
|
-
const marker = index === selected ? '●' : '○';
|
|
1989
|
-
output.write(` ${marker} ${sanitizeTerminalText(label(item, index))}\n`);
|
|
1990
|
-
});
|
|
1991
|
-
rendered = true;
|
|
1992
|
-
}
|
|
1993
|
-
render();
|
|
1994
|
-
output.write('\x1b[?25l');
|
|
1995
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
1996
|
-
function cleanup() {
|
|
1997
|
-
input.off('keypress', onKeypress);
|
|
1998
|
-
if (rawInput.setRawMode && !wasRaw) {
|
|
1999
|
-
rawInput.setRawMode(false);
|
|
2000
|
-
}
|
|
2001
|
-
input.pause();
|
|
2002
|
-
output.write('\x1b[?25h');
|
|
2003
|
-
printLine('');
|
|
2004
|
-
}
|
|
2005
|
-
function onKeypress(_chunk, key) {
|
|
2006
|
-
if (key.ctrl && key.name === 'c') {
|
|
2007
|
-
cleanup();
|
|
2008
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
if (key.name === 'escape') {
|
|
2012
|
-
cleanup();
|
|
2013
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
2014
|
-
return;
|
|
2015
|
-
}
|
|
2016
|
-
if (key.name === 'up') {
|
|
2017
|
-
selected = (selected - 1 + items.length) % items.length;
|
|
2018
|
-
render();
|
|
2019
|
-
return;
|
|
2020
|
-
}
|
|
2021
|
-
if (key.name === 'down') {
|
|
2022
|
-
selected = (selected + 1) % items.length;
|
|
2023
|
-
render();
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
2027
|
-
const item = items[selected];
|
|
2028
|
-
cleanup();
|
|
2029
|
-
resolvePromise(item);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
input.on('keypress', onKeypress);
|
|
2033
|
-
input.resume();
|
|
2034
|
-
rawInput.setRawMode?.(true);
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
1979
|
async function promptKeyboardMultiSelect(title, items, label, defaultSelected) {
|
|
2038
1980
|
if (items.length === 0) {
|
|
2039
1981
|
return [];
|
|
@@ -2162,13 +2104,13 @@ function writeWorkspaceValidationLog(workspaceRoot, findings, dryRun) {
|
|
|
2162
2104
|
}
|
|
2163
2105
|
function printWorkspaceFindings(findings) {
|
|
2164
2106
|
if (findings.length === 0) {
|
|
2165
|
-
printLine('
|
|
2107
|
+
printLine('Validation passed.');
|
|
2166
2108
|
return;
|
|
2167
2109
|
}
|
|
2168
|
-
printLine('
|
|
2110
|
+
printLine('Validation findings:');
|
|
2169
2111
|
for (const finding of findings) {
|
|
2170
2112
|
const path = finding.path ? ` ${finding.path}` : '';
|
|
2171
|
-
printLine(
|
|
2113
|
+
printLine(`- ${finding.severity.toUpperCase()} ${finding.code}:${path} ${finding.message}`);
|
|
2172
2114
|
}
|
|
2173
2115
|
}
|
|
2174
2116
|
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]`;
|
|
@@ -2495,15 +2437,7 @@ function skillsPathCommand() {
|
|
|
2495
2437
|
}
|
|
2496
2438
|
printLine(hasArg('--skill-md') ? source.skillMd : source.root);
|
|
2497
2439
|
}
|
|
2498
|
-
function
|
|
2499
|
-
const baseUrl = getBaseUrl();
|
|
2500
|
-
return {
|
|
2501
|
-
// ALI-1090: an env bearer (ORIZU_TOKEN / ORIZU_TOKEN_FILE) is signed-in too.
|
|
2502
|
-
state: hasResolvableAuth(baseUrl) ? 'signed-in' : 'signed-out',
|
|
2503
|
-
baseUrl,
|
|
2504
|
-
};
|
|
2505
|
-
}
|
|
2506
|
-
async function resolveSetupTeam(teamSlugArg, createTeamName, noInput, dryRun, workspaceTeamSlug) {
|
|
2440
|
+
async function resolveSetupTeam(teamSlugArg, createTeamName, noInput, dryRun, workspaceTeamSlug, request = authedFetch) {
|
|
2507
2441
|
if (teamSlugArg && createTeamName) {
|
|
2508
2442
|
throw new Error('Use either --team <slug> or --create-team <name>, not both.');
|
|
2509
2443
|
}
|
|
@@ -2516,11 +2450,11 @@ async function resolveSetupTeam(teamSlugArg, createTeamName, noInput, dryRun, wo
|
|
|
2516
2450
|
const name = createTeamName.trim();
|
|
2517
2451
|
if (!name)
|
|
2518
2452
|
throw new Error('--create-team requires a non-empty team name.');
|
|
2519
|
-
const team = await createTeamOnServer(name);
|
|
2520
|
-
printLine(`
|
|
2453
|
+
const team = await createTeamOnServer(name, request);
|
|
2454
|
+
printLine(`Created team ${sanitizeTerminalText(team.slug)}`);
|
|
2521
2455
|
return team;
|
|
2522
2456
|
}
|
|
2523
|
-
const teams = await fetchTeams();
|
|
2457
|
+
const teams = await fetchTeams(request);
|
|
2524
2458
|
const normalizedTeamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null;
|
|
2525
2459
|
if (normalizedTeamSlug) {
|
|
2526
2460
|
const team = teams.find(candidate => candidate.slug === normalizedTeamSlug);
|
|
@@ -2542,17 +2476,17 @@ async function resolveSetupTeam(teamSlugArg, createTeamName, noInput, dryRun, wo
|
|
|
2542
2476
|
if (choice.kind === 'team') {
|
|
2543
2477
|
return choice.team;
|
|
2544
2478
|
}
|
|
2545
|
-
const name = await askText('
|
|
2479
|
+
const name = await askText('Team name?', '');
|
|
2546
2480
|
if (!name) {
|
|
2547
2481
|
throw new Error('Team name is required to create a team.');
|
|
2548
2482
|
}
|
|
2549
2483
|
if (workspaceTeamSlug)
|
|
2550
2484
|
throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Create the new team from another directory.`);
|
|
2551
|
-
const team = await createTeamOnServer(name);
|
|
2552
|
-
printLine(`
|
|
2485
|
+
const team = await createTeamOnServer(name, request);
|
|
2486
|
+
printLine(`Created team ${sanitizeTerminalText(team.slug)}`);
|
|
2553
2487
|
return team;
|
|
2554
2488
|
}
|
|
2555
|
-
async function resolveSetupProjects(team, projectSlugArg, createProjectName, noInput, dryRun) {
|
|
2489
|
+
async function resolveSetupProjects(team, projectSlugArg, createProjectName, noInput, dryRun, request) {
|
|
2556
2490
|
if (projectSlugArg && createProjectName) {
|
|
2557
2491
|
throw new Error('Use either --project <slug> or --create-project <name>, not both.');
|
|
2558
2492
|
}
|
|
@@ -2563,7 +2497,7 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
|
|
|
2563
2497
|
if (createProjectName && !canCreateProject) {
|
|
2564
2498
|
throw new Error(`Only team admins can create projects in '${team.slug}'. Choose an existing project or ask a team admin to create one.`);
|
|
2565
2499
|
}
|
|
2566
|
-
const projects = await fetchProjects(team.slug);
|
|
2500
|
+
const projects = await fetchProjects(team.slug, request);
|
|
2567
2501
|
if (createProjectName) {
|
|
2568
2502
|
if (dryRun) {
|
|
2569
2503
|
throw new Error('--create-project cannot be used with --dry-run because setup cannot preview a server-generated project slug.');
|
|
@@ -2571,8 +2505,8 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
|
|
|
2571
2505
|
const name = createProjectName.trim();
|
|
2572
2506
|
if (!name)
|
|
2573
2507
|
throw new Error('--create-project requires a non-empty project name.');
|
|
2574
|
-
const selected = await createProjectOnServer(team.slug, name);
|
|
2575
|
-
printLine(`
|
|
2508
|
+
const selected = await createProjectOnServer(team.slug, name, request);
|
|
2509
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`);
|
|
2576
2510
|
return { selected, projects: [...projects, selected] };
|
|
2577
2511
|
}
|
|
2578
2512
|
const normalizedProjectSlug = projectSlugArg ? normalizeSlugInput(projectSlugArg) : null;
|
|
@@ -2586,12 +2520,12 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
|
|
|
2586
2520
|
if (projects.length === 0) {
|
|
2587
2521
|
if (!canCreateProject)
|
|
2588
2522
|
throw new Error(`Team '${team.slug}' has no accessible projects, and only team admins can create one.`);
|
|
2589
|
-
printLine(`
|
|
2590
|
-
const name = await askText('
|
|
2523
|
+
printLine(`Team '${sanitizeTerminalText(team.slug)}' has no projects yet. Let's create the first one.`);
|
|
2524
|
+
const name = await askText('Project name?', '');
|
|
2591
2525
|
if (!name)
|
|
2592
2526
|
throw new Error('Project name is required to create a project.');
|
|
2593
|
-
const selected = await createProjectOnServer(team.slug, name);
|
|
2594
|
-
printLine(`
|
|
2527
|
+
const selected = await createProjectOnServer(team.slug, name, request);
|
|
2528
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`);
|
|
2595
2529
|
return { selected, projects: [selected] };
|
|
2596
2530
|
}
|
|
2597
2531
|
const choices = [
|
|
@@ -2604,11 +2538,11 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
|
|
|
2604
2538
|
if (choice.kind === 'project') {
|
|
2605
2539
|
return { selected: choice.project, projects };
|
|
2606
2540
|
}
|
|
2607
|
-
const name = await askText('
|
|
2541
|
+
const name = await askText('Project name?', '');
|
|
2608
2542
|
if (!name)
|
|
2609
2543
|
throw new Error('Project name is required to create a project.');
|
|
2610
|
-
const selected = await createProjectOnServer(team.slug, name);
|
|
2611
|
-
printLine(`
|
|
2544
|
+
const selected = await createProjectOnServer(team.slug, name, request);
|
|
2545
|
+
printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`);
|
|
2612
2546
|
return { selected, projects: [...projects, selected] };
|
|
2613
2547
|
}
|
|
2614
2548
|
function projectSeedsFromProjects(projects) {
|
|
@@ -2621,32 +2555,6 @@ function projectSeedsFromProjects(projects) {
|
|
|
2621
2555
|
function formatProjectSetupProgress(count) {
|
|
2622
2556
|
return `${count} project${count === 1 ? '' : 's'} being set up in workspace...`;
|
|
2623
2557
|
}
|
|
2624
|
-
const SETUP_NATIVE_SKILL_CHOICES = [
|
|
2625
|
-
{ agent: 'claude', label: 'Claude Code', directory: ['.claude'], binary: 'claude' },
|
|
2626
|
-
{ agent: 'devin', label: 'Devin', directory: ['.devin', 'skills'] },
|
|
2627
|
-
{ agent: 'droid', label: 'Droid', directory: ['.factory', 'skills'] },
|
|
2628
|
-
{ agent: 'grok', label: 'Grok Build', directory: ['.grok', 'skills'] },
|
|
2629
|
-
{ agent: 'windsurf', label: 'Windsurf', directory: ['.windsurf', 'skills'] },
|
|
2630
|
-
{ agent: 'opencode', label: 'OpenCode', directory: ['.config', 'opencode', 'skills'], binary: 'opencode' },
|
|
2631
|
-
];
|
|
2632
|
-
function setupNativeTargetForAgent(agent) {
|
|
2633
|
-
if (agent === 'codex' || agent === 'pi')
|
|
2634
|
-
return null;
|
|
2635
|
-
return getTargetForAgent(agent, 'global');
|
|
2636
|
-
}
|
|
2637
|
-
function resolveSetupSkillHome() {
|
|
2638
|
-
return resolve(process.env.HOME || homedir());
|
|
2639
|
-
}
|
|
2640
|
-
function detectedSetupSkillChoices(homeDir) {
|
|
2641
|
-
return SETUP_NATIVE_SKILL_CHOICES
|
|
2642
|
-
.filter(choice => existsSync(join(homeDir, ...choice.directory))
|
|
2643
|
-
|| Boolean(choice.binary && findExecutable(choice.binary)))
|
|
2644
|
-
.map(choice => ({
|
|
2645
|
-
agent: choice.agent,
|
|
2646
|
-
label: choice.label,
|
|
2647
|
-
target: getTargetForAgent(choice.agent, 'global'),
|
|
2648
|
-
}));
|
|
2649
|
-
}
|
|
2650
2558
|
function setupSkillTargetsFromArgs() {
|
|
2651
2559
|
const targets = [];
|
|
2652
2560
|
for (const agent of getArgs('--agent')) {
|
|
@@ -2706,22 +2614,58 @@ async function setupCommand() {
|
|
|
2706
2614
|
// Step 1: login
|
|
2707
2615
|
printLine('Step 1: Login');
|
|
2708
2616
|
let auth = describeSetupAuthState();
|
|
2617
|
+
let setupAuthContext = null;
|
|
2709
2618
|
if (hasArg('--skip-login')) {
|
|
2710
|
-
printLine('
|
|
2619
|
+
printLine('Skipped (--skip-login).');
|
|
2711
2620
|
}
|
|
2712
2621
|
else if (auth.state === 'signed-in') {
|
|
2713
|
-
|
|
2622
|
+
if (!validateOnly) {
|
|
2623
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun });
|
|
2624
|
+
}
|
|
2625
|
+
if (!noInput && !validateOnly) {
|
|
2626
|
+
const confirmation = await confirmExistingSetupAccount(setupAuthContext, email => promptKeyboardSelect('Confirm the account to use for setup', ['continue', 'switch'], item => item === 'continue'
|
|
2627
|
+
? `Continue as ${email}`
|
|
2628
|
+
: 'Log in with a different account'), async () => {
|
|
2629
|
+
const context = setupAuthContext;
|
|
2630
|
+
const logoutResult = await context.logout();
|
|
2631
|
+
if (logoutResult.remoteError) {
|
|
2632
|
+
const localOutcome = logoutResult.localCredentialCleared
|
|
2633
|
+
? 'Local credentials were cleared.'
|
|
2634
|
+
: 'Local credentials were not cleared because a newer login is active.';
|
|
2635
|
+
console.warn(`Warning: remote logout failed: ${logoutResult.remoteError} ${localOutcome}`);
|
|
2636
|
+
}
|
|
2637
|
+
else if (!logoutResult.localCredentialCleared) {
|
|
2638
|
+
console.warn('Warning: local credentials were not cleared because a newer login is active.');
|
|
2639
|
+
}
|
|
2640
|
+
printLine(logoutResult.localCredentialCleared
|
|
2641
|
+
? `Logged out from ${sanitizeTerminalText(context.baseUrl)}.`
|
|
2642
|
+
: `Kept the newer login for ${sanitizeTerminalText(context.baseUrl)}.`);
|
|
2643
|
+
await login(context.baseUrl);
|
|
2644
|
+
}, { dryRun });
|
|
2645
|
+
if (confirmation === 'switched') {
|
|
2646
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun });
|
|
2647
|
+
}
|
|
2648
|
+
auth = { state: 'signed-in', baseUrl: setupAuthContext.baseUrl };
|
|
2649
|
+
}
|
|
2650
|
+
else {
|
|
2651
|
+
printLine(`Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`);
|
|
2652
|
+
}
|
|
2714
2653
|
}
|
|
2715
2654
|
else if (noInput || dryRun) {
|
|
2716
|
-
printLine('
|
|
2655
|
+
printLine('Not signed in. Run `orizu login` to authenticate.');
|
|
2717
2656
|
}
|
|
2718
|
-
else if (await waitForAnyKeyOrEscape('
|
|
2657
|
+
else if (await waitForAnyKeyOrEscape('Press any key to connect your account, or Esc to cancel.')) {
|
|
2719
2658
|
await login();
|
|
2720
2659
|
auth = describeSetupAuthState();
|
|
2721
2660
|
}
|
|
2722
2661
|
else {
|
|
2723
2662
|
throw new Error('Setup cancelled.');
|
|
2724
2663
|
}
|
|
2664
|
+
if (auth.state === 'signed-in' && !validateOnly && !setupAuthContext) {
|
|
2665
|
+
setupAuthContext = captureAuthenticatedRequestContext({ allowRefresh: !dryRun });
|
|
2666
|
+
}
|
|
2667
|
+
if (setupAuthContext)
|
|
2668
|
+
auth = { state: 'signed-in', baseUrl: setupAuthContext.baseUrl };
|
|
2725
2669
|
const serverCreateRequested = createTeamArg !== null || createProjectArg !== null;
|
|
2726
2670
|
if ((noInput || serverCreateRequested) && auth.state === 'signed-out' && !dryRun && !validateOnly && !fix) {
|
|
2727
2671
|
throw new Error('Non-interactive setup requires authentication. Run `orizu login --headless`, then retry `orizu setup`.');
|
|
@@ -2731,17 +2675,18 @@ async function setupCommand() {
|
|
|
2731
2675
|
let selectedProject = null;
|
|
2732
2676
|
let setupProjects = [];
|
|
2733
2677
|
if (auth.state === 'signed-in' && !validateOnly) {
|
|
2734
|
-
|
|
2678
|
+
const setupRequest = setupAuthContext.fetch.bind(setupAuthContext);
|
|
2679
|
+
setupTeam = await resolveSetupTeam(teamArg, createTeamArg, noInput, dryRun, workspaceTeamSlug, setupRequest);
|
|
2735
2680
|
if (workspaceTeamSlug && setupTeam.slug !== workspaceTeamSlug) {
|
|
2736
2681
|
throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Run setup in another directory to set up team '${setupTeam.slug}'.`);
|
|
2737
2682
|
}
|
|
2738
2683
|
if (fix) {
|
|
2739
|
-
setupProjects = await fetchProjects(setupTeam.slug);
|
|
2684
|
+
setupProjects = await fetchProjects(setupTeam.slug, setupRequest);
|
|
2740
2685
|
if (setupProjects.length === 0)
|
|
2741
2686
|
throw new Error(`Team '${setupTeam.slug}' has no projects to restore.`);
|
|
2742
2687
|
}
|
|
2743
2688
|
else {
|
|
2744
|
-
const selection = await resolveSetupProjects(setupTeam, projectArg, createProjectArg, noInput, dryRun);
|
|
2689
|
+
const selection = await resolveSetupProjects(setupTeam, projectArg, createProjectArg, noInput, dryRun, setupRequest);
|
|
2745
2690
|
selectedProject = selection.selected;
|
|
2746
2691
|
setupProjects = selection.projects;
|
|
2747
2692
|
printLine(`Selected project ${sanitizeTerminalText(`${setupTeam.slug}/${selectedProject.slug}`)}`);
|
|
@@ -2754,7 +2699,7 @@ async function setupCommand() {
|
|
|
2754
2699
|
let workspaceResult = null;
|
|
2755
2700
|
let validationLogPath = null;
|
|
2756
2701
|
if (hasArg('--no-workspace') && !validateOnly && !fix) {
|
|
2757
|
-
printLine('
|
|
2702
|
+
printLine('Skipped (--no-workspace).');
|
|
2758
2703
|
}
|
|
2759
2704
|
else {
|
|
2760
2705
|
const wantsWorkspace = validateOnly
|
|
@@ -2772,20 +2717,20 @@ async function setupCommand() {
|
|
|
2772
2717
|
teamSlug = setupTeam.slug;
|
|
2773
2718
|
teamId = setupTeam.id;
|
|
2774
2719
|
projects = projectSeedsFromProjects(setupProjects);
|
|
2775
|
-
printLine(
|
|
2720
|
+
printLine(formatProjectSetupProgress(projects.length));
|
|
2776
2721
|
}
|
|
2777
2722
|
else if (!teamSlug && !noInput && !validateOnly) {
|
|
2778
|
-
teamSlug = await askText('
|
|
2723
|
+
teamSlug = await askText('Team slug?', 'local-team');
|
|
2779
2724
|
}
|
|
2780
2725
|
if (auth.state !== 'signed-in' && (!teamSlug || !projectSlug) && noInput && hasArg('--workspace')) {
|
|
2781
|
-
printLine('
|
|
2726
|
+
printLine('Using starter local-only team/project. Pass --team with authentication to materialize server projects.');
|
|
2782
2727
|
}
|
|
2783
2728
|
if (projectSlug && auth.state !== 'signed-in') {
|
|
2784
2729
|
projects = [{ slug: projectSlug }];
|
|
2785
2730
|
}
|
|
2786
2731
|
if (auth.state !== 'signed-in' && !validateOnly) {
|
|
2787
2732
|
const localProjectCount = projects?.length || 1;
|
|
2788
|
-
printLine(
|
|
2733
|
+
printLine(formatProjectSetupProgress(localProjectCount));
|
|
2789
2734
|
}
|
|
2790
2735
|
const result = initOrizuWorkspace({
|
|
2791
2736
|
workspaceRoot,
|
|
@@ -2806,14 +2751,14 @@ async function setupCommand() {
|
|
|
2806
2751
|
workspaceState = result.state;
|
|
2807
2752
|
if (dryRun || verbose) {
|
|
2808
2753
|
for (const action of result.actions) {
|
|
2809
|
-
printLine(
|
|
2754
|
+
printLine(`${dryRun ? 'Would ' : 'Did '}${action}`);
|
|
2810
2755
|
}
|
|
2811
2756
|
}
|
|
2812
2757
|
else if (result.actions.length > 0) {
|
|
2813
|
-
printLine(validateOnly ? '
|
|
2758
|
+
printLine(validateOnly ? 'Workspace validation complete' : 'Workspace setup complete');
|
|
2814
2759
|
}
|
|
2815
2760
|
else if (!validateOnly) {
|
|
2816
|
-
printLine('
|
|
2761
|
+
printLine('Workspace setup complete');
|
|
2817
2762
|
}
|
|
2818
2763
|
validationLogPath = writeWorkspaceValidationLog(result.root, result.findings, dryRun);
|
|
2819
2764
|
if (verbose) {
|
|
@@ -2824,7 +2769,7 @@ async function setupCommand() {
|
|
|
2824
2769
|
}
|
|
2825
2770
|
}
|
|
2826
2771
|
else {
|
|
2827
|
-
printLine(
|
|
2772
|
+
printLine('Skipped. Rerun with --workspace to initialize the workspace contract.');
|
|
2828
2773
|
}
|
|
2829
2774
|
}
|
|
2830
2775
|
printLine('');
|
|
@@ -2832,28 +2777,21 @@ async function setupCommand() {
|
|
|
2832
2777
|
let installOutcomes = [];
|
|
2833
2778
|
printLine('Step 3: Install coding agent skills');
|
|
2834
2779
|
if (hasArg('--no-install') || validateOnly || fix) {
|
|
2835
|
-
printLine(hasArg('--no-install') ? '
|
|
2780
|
+
printLine(hasArg('--no-install') ? 'Skipped (--no-install).' : 'Skipped for validation/repair.');
|
|
2836
2781
|
}
|
|
2837
2782
|
else {
|
|
2838
2783
|
let targets = setupSkillTargetsFromArgs();
|
|
2839
2784
|
if (targets.length === 0 && !noInput && !dryRun) {
|
|
2840
|
-
if (await askYesNo('
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
if (choices.length > 0) {
|
|
2844
|
-
const selectedChoices = await promptKeyboardMultiSelect('Choose additional native agent installs', choices, choice => choice.label, () => true);
|
|
2845
|
-
for (const choice of selectedChoices) {
|
|
2846
|
-
if (!targets.includes(choice.target))
|
|
2847
|
-
targets.push(choice.target);
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2785
|
+
if (await askYesNo('Install the Orizu skill for your coding agents?', true)) {
|
|
2786
|
+
const selectedChoices = await promptKeyboardMultiSelect('Choose skill installation destinations', detectedSetupSkillChoices(setupSkillHome), choice => choice.label, () => true);
|
|
2787
|
+
targets = selectedChoices.map(choice => choice.target);
|
|
2850
2788
|
}
|
|
2851
2789
|
}
|
|
2852
2790
|
if (targets.length === 0) {
|
|
2853
|
-
printLine('
|
|
2791
|
+
printLine('Skipped skill install.');
|
|
2854
2792
|
}
|
|
2855
2793
|
else {
|
|
2856
|
-
printLine('
|
|
2794
|
+
printLine('Installing skills...');
|
|
2857
2795
|
installOutcomes = await applySkillInstallTargets(targets, {
|
|
2858
2796
|
mode: installMode,
|
|
2859
2797
|
skipConfirm: true,
|
|
@@ -2864,18 +2802,18 @@ async function setupCommand() {
|
|
|
2864
2802
|
printResults: false,
|
|
2865
2803
|
});
|
|
2866
2804
|
if (dryRun) {
|
|
2867
|
-
printLine('
|
|
2805
|
+
printLine('Dry run: no skill files were changed.');
|
|
2868
2806
|
}
|
|
2869
2807
|
for (const outcome of installOutcomes) {
|
|
2870
2808
|
const label = setupSkillLabel(outcome.target);
|
|
2871
2809
|
if (outcome.action === 'failed') {
|
|
2872
|
-
printLine(
|
|
2810
|
+
printLine(`${label} failed: ${sanitizeSetupHumanInlineText(outcome.error)}`);
|
|
2873
2811
|
}
|
|
2874
2812
|
else if (outcome.action === 'skipped') {
|
|
2875
|
-
printLine(
|
|
2813
|
+
printLine(`${label} skipped`);
|
|
2876
2814
|
}
|
|
2877
2815
|
else {
|
|
2878
|
-
printLine(
|
|
2816
|
+
printLine(`${label} installed`);
|
|
2879
2817
|
}
|
|
2880
2818
|
}
|
|
2881
2819
|
}
|
|
@@ -2894,51 +2832,64 @@ async function setupCommand() {
|
|
|
2894
2832
|
printLine('--- prompt end ---');
|
|
2895
2833
|
printLine('');
|
|
2896
2834
|
}
|
|
2897
|
-
const detected = Object.
|
|
2898
|
-
.filter(
|
|
2835
|
+
const detected = Object.entries(AGENT_LAUNCH_SPECS)
|
|
2836
|
+
.filter(([, spec]) => findExecutable(spec.command))
|
|
2837
|
+
.map(([agent]) => agent);
|
|
2899
2838
|
let launchAgent = getArg('--launch');
|
|
2900
|
-
|
|
2901
|
-
|
|
2839
|
+
let completedInteractiveHandoff = false;
|
|
2840
|
+
if (launchAgent && !(launchAgent in AGENT_LAUNCH_SPECS)) {
|
|
2841
|
+
throw new Error(`Unknown --launch agent '${launchAgent}'. Choices: ${Object.keys(AGENT_LAUNCH_SPECS).join(', ')}.`);
|
|
2902
2842
|
}
|
|
2903
|
-
if (!launchAgent && !noInput && isInteractiveTerminal() &&
|
|
2904
|
-
const launchChoice = await promptKeyboardSelect('Choose an agent to launch', [
|
|
2905
|
-
|
|
2843
|
+
if (!launchAgent && !noInput && isInteractiveTerminal() && !dryRun) {
|
|
2844
|
+
const launchChoice = await promptKeyboardSelect('Choose an agent to launch', [...detected, 'other'], agent => agent === 'other' ? 'Other' : AGENT_LAUNCH_SPECS[agent].displayLabel, { onEscape: () => 'escape', escapeLabel: 'skip' });
|
|
2845
|
+
completedInteractiveHandoff = true;
|
|
2846
|
+
if (launchChoice === 'other') {
|
|
2847
|
+
if (!handoffRequested) {
|
|
2848
|
+
printLine('--- prompt start ---');
|
|
2849
|
+
printLine(handoffPrompt);
|
|
2850
|
+
printLine('--- prompt end ---');
|
|
2851
|
+
printLine('');
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
else if (launchChoice !== 'escape') {
|
|
2855
|
+
launchAgent = launchChoice;
|
|
2856
|
+
}
|
|
2906
2857
|
}
|
|
2907
2858
|
if (launchAgent) {
|
|
2908
|
-
const
|
|
2859
|
+
const launchSpec = AGENT_LAUNCH_SPECS[launchAgent];
|
|
2860
|
+
const binary = findExecutable(launchSpec.command);
|
|
2909
2861
|
if (inputDisabled) {
|
|
2910
|
-
printLine('
|
|
2862
|
+
printLine('Not launching: setup is non-interactive.');
|
|
2911
2863
|
}
|
|
2912
2864
|
else if (!isInteractiveTerminal()) {
|
|
2913
|
-
printLine('
|
|
2865
|
+
printLine('Not launching: --launch requires an interactive terminal.');
|
|
2914
2866
|
}
|
|
2915
2867
|
else if (!binary) {
|
|
2916
|
-
printLine(`
|
|
2868
|
+
printLine(`Not launching: '${launchSpec.command}' was not found on PATH.`);
|
|
2917
2869
|
}
|
|
2918
2870
|
else if (dryRun) {
|
|
2919
|
-
printLine(`
|
|
2871
|
+
printLine(`Would launch ${binary} with the setup prompt.`);
|
|
2920
2872
|
}
|
|
2921
|
-
else if (!getArg('--launch') || skipConfirm || await askYesNo(`
|
|
2922
|
-
printLine(`
|
|
2923
|
-
const launched = spawnSync(binary,
|
|
2873
|
+
else if (!getArg('--launch') || skipConfirm || await askYesNo(`Launch ${launchSpec.displayLabel} with the setup prompt now?`, true)) {
|
|
2874
|
+
printLine(`Launching ${binary}…`);
|
|
2875
|
+
const launched = spawnSync(binary, launchSpec.buildPromptArgs(handoffPrompt), {
|
|
2876
|
+
cwd: workspaceRoot,
|
|
2877
|
+
stdio: 'inherit',
|
|
2878
|
+
shell: false,
|
|
2879
|
+
});
|
|
2924
2880
|
if (!launched.error)
|
|
2925
2881
|
launchedAgent = launchAgent;
|
|
2926
2882
|
if (launched.error || (launched.status !== null && launched.status !== 0)) {
|
|
2927
|
-
printLine(
|
|
2883
|
+
printLine(`${launchSpec.displayLabel} exited without completing successfully.`);
|
|
2928
2884
|
}
|
|
2929
2885
|
}
|
|
2930
2886
|
}
|
|
2931
|
-
else if (detected.length === 0) {
|
|
2932
|
-
printLine(
|
|
2887
|
+
else if (detected.length === 0 && !completedInteractiveHandoff) {
|
|
2888
|
+
printLine('No launchable coding agent was found. Read https://orizu.ai/llms.txt to get started.');
|
|
2933
2889
|
}
|
|
2934
2890
|
printLine('');
|
|
2935
2891
|
}
|
|
2936
2892
|
// Setup summary (success criteria checklist)
|
|
2937
|
-
const summaryTargets = installOutcomes.length > 0
|
|
2938
|
-
? installOutcomes
|
|
2939
|
-
.filter(outcome => outcome.action !== 'failed')
|
|
2940
|
-
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target, { cwd: workspaceRoot, homeDir: setupSkillHome }) }))
|
|
2941
|
-
: [];
|
|
2942
2893
|
const source = resolveSkillSource();
|
|
2943
2894
|
if (hasJsonFlag()) {
|
|
2944
2895
|
printJson({
|
|
@@ -2984,19 +2935,8 @@ async function setupCommand() {
|
|
|
2984
2935
|
}
|
|
2985
2936
|
printLine('Setup summary');
|
|
2986
2937
|
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`);
|
|
2987
|
-
|
|
2988
|
-
printLine(
|
|
2989
|
-
for (const { outcome, status } of summaryTargets) {
|
|
2990
|
-
const modeLabel = status.mode ? `, ${status.mode}` : '';
|
|
2991
|
-
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`);
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
else {
|
|
2995
|
-
printLine(' Skills: none installed this run');
|
|
2996
|
-
}
|
|
2997
|
-
const failed = installOutcomes.filter(outcome => outcome.action === 'failed');
|
|
2998
|
-
for (const failure of failed) {
|
|
2999
|
-
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`);
|
|
2938
|
+
for (const line of renderSetupSkillSummary(installOutcomes, setupSkillHome)) {
|
|
2939
|
+
printLine(line);
|
|
3000
2940
|
}
|
|
3001
2941
|
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : getWorkspaceRoot(workspaceRoot)}`);
|
|
3002
2942
|
if (workspaceResult) {
|