orizu 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -6
- package/dist/banner.js +20 -0
- package/dist/csv.js +117 -9
- package/dist/help.js +114 -15
- package/dist/index.js +828 -67
- package/dist/setup-prompt.js +22 -0
- package/dist/skill-installer.js +243 -17
- package/dist/task-assignment-manifest.js +59 -0
- package/dist/workspace.js +76 -0
- package/package.json +5 -1
- package/src/banner.ts +21 -0
- package/src/csv.ts +140 -10
- package/src/help.ts +114 -15
- package/src/index.ts +933 -70
- package/src/setup-prompt.ts +27 -0
- package/src/skill-installer.ts +343 -17
- package/src/task-assignment-manifest.ts +96 -0
- package/src/workspace.ts +112 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/__init__.py +2 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +57 -3
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +37 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +233 -17
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +98 -17
- package/vendor/skills/orizu-cli/SKILL.md +13 -6
- package/vendor/skills/orizu-cli/references/cli-reference.md +34 -5
- package/vendor/skills/orizu-cli/references/optimization-reports.md +2 -2
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +1 -0
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +17 -5
package/dist/index.js
CHANGED
|
@@ -17,9 +17,13 @@ import { extractErrorMessage } from './error-response.js';
|
|
|
17
17
|
import { parseGlobalFlags } from './global-flags.js';
|
|
18
18
|
import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js';
|
|
19
19
|
import { runLocalAppPreview } from './preview-runtime.js';
|
|
20
|
+
import { parseAssignmentManifestJsonl } from './task-assignment-manifest.js';
|
|
20
21
|
import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
|
|
21
|
-
import { getSkillInstallPath, installSkillTarget, isSkillInstallTarget, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
|
|
22
|
+
import { computeSkillContentHash, getSkillInstallPath, getSkillTargetStatus, getTargetForAgent, installSkillTarget, isSkillInstallAgent, isSkillInstallTarget, resolveSkillSource, SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
|
|
23
|
+
import { renderBanner } from './banner.js';
|
|
22
24
|
import { formatTaskCreateError } from './task-create-error.js';
|
|
25
|
+
import { renderAgentSetupPrompt } from './setup-prompt.js';
|
|
26
|
+
import { getWorkspaceRoot, initOrizuWorkspace, WORKSPACE_DIR_NAME, workspaceExists, } from './workspace.js';
|
|
23
27
|
const MAX_README_LENGTH = 200_000;
|
|
24
28
|
function getCliVersion() {
|
|
25
29
|
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -34,14 +38,22 @@ function printLine(message = '') {
|
|
|
34
38
|
function printVersion() {
|
|
35
39
|
printLine(`orizu ${getCliVersion()}`);
|
|
36
40
|
}
|
|
41
|
+
function printBannerIfInteractive() {
|
|
42
|
+
if (!process.stdout.isTTY) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
printLine(renderBanner());
|
|
46
|
+
printLine('');
|
|
47
|
+
}
|
|
37
48
|
function printUsage() {
|
|
49
|
+
printBannerIfInteractive();
|
|
38
50
|
printLine(renderRootHelp());
|
|
39
51
|
}
|
|
40
52
|
function printPreviewUsage() {
|
|
41
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>]`);
|
|
42
54
|
}
|
|
43
55
|
function printOptimizationUsage() {
|
|
44
|
-
printLine(`\nOptimization lifecycle commands:\n\n orizu optimizations start --project <team/project> --optimizer-version <id> --prompt-version <id[,id]> --selection-scorer <id> [--reflection-scorer <id>] [--pareto-scorer <id>] [--best-scorer <id>] --dataset-version <id> --split-set <id> [--train-split <name>] [--validation-split <name>] [--metadata <json|@file>] [--json]\n orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> --candidate-version-id <id> --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--num-threads auto|N] [--log-dir logs] [--no-skip-perfect-parent-reflection]\n orizu optimizations export <run-id> [--out <path>] [--json]\n orizu optimizations pause <run-id> [--reason <text>] [--json]\n orizu optimizations resume <run-id> [--json]\n orizu optimizations finish <run-id> [--best-score <n>] [--best-candidate <id>] [--result-prompt-version <id>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations fail <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations cancel <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--json]`);
|
|
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]`);
|
|
45
57
|
}
|
|
46
58
|
let cliArgs = process.argv.slice(2);
|
|
47
59
|
let cliJsonOutput = false;
|
|
@@ -204,14 +216,16 @@ function printPromptSummaries(items, emptyMessage) {
|
|
|
204
216
|
id: sanitizeTerminalText(item.id),
|
|
205
217
|
name: sanitizeTerminalText(item.name),
|
|
206
218
|
role: sanitizeTerminalText(item.role),
|
|
219
|
+
status: sanitizeTerminalText(item.status || 'active'),
|
|
207
220
|
}));
|
|
208
221
|
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length));
|
|
209
222
|
const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length));
|
|
210
223
|
const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length));
|
|
211
|
-
|
|
212
|
-
printLine(`${'
|
|
224
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length));
|
|
225
|
+
printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)} ${'STATUS'.padEnd(statusWidth)}`);
|
|
226
|
+
printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)} ${'-'.repeat(statusWidth)}`);
|
|
213
227
|
rows.forEach(row => {
|
|
214
|
-
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)}`);
|
|
228
|
+
printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)} ${row.status.padEnd(statusWidth)}`);
|
|
215
229
|
});
|
|
216
230
|
}
|
|
217
231
|
function printScorerSummaries(items) {
|
|
@@ -703,6 +717,10 @@ async function login() {
|
|
|
703
717
|
const baseUrl = hasArg('--no-prompt-if-logged-in') ? getBaseUrl() : resolveLoginBaseUrl();
|
|
704
718
|
assertSecureTokenTransport(baseUrl);
|
|
705
719
|
if (hasArg('--no-prompt-if-logged-in') && getServerCredentials(baseUrl)) {
|
|
720
|
+
if (hasJsonFlag()) {
|
|
721
|
+
printJson({ status: 'already-logged-in', server: baseUrl });
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
706
724
|
printLine(`Already logged in to ${sanitizeTerminalText(baseUrl)}.`);
|
|
707
725
|
return;
|
|
708
726
|
}
|
|
@@ -781,6 +799,14 @@ async function login() {
|
|
|
781
799
|
credentialType: 'pat',
|
|
782
800
|
apiKey: loginData.apiKey,
|
|
783
801
|
});
|
|
802
|
+
if (hasJsonFlag()) {
|
|
803
|
+
printJson({
|
|
804
|
+
status: 'logged-in',
|
|
805
|
+
server: baseUrl,
|
|
806
|
+
user: { id: loginData.user.id, email: loginData.user.email ?? null },
|
|
807
|
+
});
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
784
810
|
printLine(`Logged in as ${sanitizeTerminalText(loginData.user.email ?? loginData.user.id)}`);
|
|
785
811
|
}
|
|
786
812
|
async function whoami() {
|
|
@@ -789,12 +815,20 @@ async function whoami() {
|
|
|
789
815
|
throw new Error(`whoami failed: ${await response.text()}`);
|
|
790
816
|
}
|
|
791
817
|
const data = await response.json();
|
|
818
|
+
if (hasJsonFlag()) {
|
|
819
|
+
printJson({ user: data.user, server: getBaseUrl() });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
792
822
|
printLine(sanitizeTerminalText(data.user.email ?? data.user.id));
|
|
793
823
|
}
|
|
794
824
|
async function logout() {
|
|
795
825
|
const baseUrl = getBaseUrl();
|
|
796
826
|
const credentials = getServerCredentials(baseUrl);
|
|
797
827
|
if (!credentials) {
|
|
828
|
+
if (hasJsonFlag()) {
|
|
829
|
+
printJson({ status: 'already-logged-out', server: baseUrl });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
798
832
|
printLine(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`);
|
|
799
833
|
return;
|
|
800
834
|
}
|
|
@@ -820,6 +854,10 @@ async function logout() {
|
|
|
820
854
|
remoteLogoutError = sanitizeTerminalText(message).slice(0, 180);
|
|
821
855
|
}
|
|
822
856
|
clearServerCredentials(baseUrl);
|
|
857
|
+
if (hasJsonFlag()) {
|
|
858
|
+
printJson({ status: 'logged-out', server: baseUrl, remoteLogoutError });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
823
861
|
if (remoteLogoutError) {
|
|
824
862
|
console.warn(`Warning: remote logout failed: ${remoteLogoutError}`);
|
|
825
863
|
}
|
|
@@ -832,6 +870,15 @@ async function printEnv() {
|
|
|
832
870
|
const project = projectArg ? await resolveProjectSelection(projectArg) : null;
|
|
833
871
|
const projectId = project?.id || getArg('--project-id') || process.env.ORIZU_PROJECT_ID || '';
|
|
834
872
|
const projectSlug = project ? `${project.teamSlug}/${project.slug}` : process.env.ORIZU_PROJECT || '';
|
|
873
|
+
if (hasJsonFlag()) {
|
|
874
|
+
printJson({
|
|
875
|
+
ORIZU_API_URL: baseUrl,
|
|
876
|
+
ORIZU_TOKEN: token,
|
|
877
|
+
ORIZU_PROJECT_ID: projectId,
|
|
878
|
+
...(projectSlug ? { ORIZU_PROJECT: projectSlug } : {}),
|
|
879
|
+
});
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
835
882
|
printLine(`export ORIZU_API_URL=${shellQuote(baseUrl)}`);
|
|
836
883
|
printLine(`export ORIZU_TOKEN=${shellQuote(token)}`);
|
|
837
884
|
printLine(`export ORIZU_PROJECT_ID=${shellQuote(projectId)}`);
|
|
@@ -868,24 +915,46 @@ async function logOptimizationEvent() {
|
|
|
868
915
|
throw new Error(`Failed to log event: ${await response.text()}`);
|
|
869
916
|
}
|
|
870
917
|
const data = await parseJsonResponse(response, 'Optimization event log');
|
|
918
|
+
if (hasJsonFlag()) {
|
|
919
|
+
printJson({ eventId: data.eventId });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
871
922
|
printLine(`Logged event ${sanitizeTerminalText(data.eventId)}`);
|
|
872
923
|
}
|
|
873
924
|
async function listPrompts() {
|
|
874
925
|
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
875
|
-
const
|
|
926
|
+
const status = getArg('--status') || 'active';
|
|
927
|
+
if (!['active', 'archived', 'all'].includes(status)) {
|
|
928
|
+
throw new Error('Usage: orizu prompts list --project <team/project> [--status active|archived|all]');
|
|
929
|
+
}
|
|
930
|
+
const params = new URLSearchParams({ project, status });
|
|
931
|
+
const response = await authedFetch(`/api/cli/prompts?${params.toString()}`);
|
|
876
932
|
if (!response.ok) {
|
|
877
933
|
throw new Error(`Failed to fetch prompts: ${await response.text()}`);
|
|
878
934
|
}
|
|
879
935
|
const data = await parseJsonResponse(response, 'Prompts list');
|
|
936
|
+
if (hasJsonFlag()) {
|
|
937
|
+
printJson(data);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
880
940
|
printPromptSummaries(data.prompts, 'No prompts found.');
|
|
881
941
|
}
|
|
882
942
|
async function listJudges() {
|
|
883
943
|
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
884
|
-
const
|
|
944
|
+
const status = getArg('--status') || 'active';
|
|
945
|
+
if (!['active', 'archived', 'all'].includes(status)) {
|
|
946
|
+
throw new Error('Usage: orizu judges list --project <team/project> [--status active|archived|all]');
|
|
947
|
+
}
|
|
948
|
+
const params = new URLSearchParams({ project, status });
|
|
949
|
+
const response = await authedFetch(`/api/cli/judges?${params.toString()}`);
|
|
885
950
|
if (!response.ok) {
|
|
886
951
|
throw new Error(`Failed to fetch judges: ${await response.text()}`);
|
|
887
952
|
}
|
|
888
953
|
const data = await parseJsonResponse(response, 'Judges list');
|
|
954
|
+
if (hasJsonFlag()) {
|
|
955
|
+
printJson(data);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
889
958
|
printPromptSummaries(data.judges, 'No judges found.');
|
|
890
959
|
}
|
|
891
960
|
async function listPromptComments() {
|
|
@@ -922,6 +991,10 @@ async function listScorers() {
|
|
|
922
991
|
throw new Error(`Failed to fetch scorers: ${await response.text()}`);
|
|
923
992
|
}
|
|
924
993
|
const data = await parseJsonResponse(response, 'Scorers list');
|
|
994
|
+
if (hasJsonFlag()) {
|
|
995
|
+
printJson(data);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
925
998
|
printScorerSummaries(data.scorers);
|
|
926
999
|
}
|
|
927
1000
|
async function registerScorer() {
|
|
@@ -1203,6 +1276,28 @@ async function setPromptLabel() {
|
|
|
1203
1276
|
}
|
|
1204
1277
|
printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`);
|
|
1205
1278
|
}
|
|
1279
|
+
async function setPromptArchivedState(archived) {
|
|
1280
|
+
const promptRef = getPositionalArg(2);
|
|
1281
|
+
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
1282
|
+
const command = archived ? 'archive' : 'restore';
|
|
1283
|
+
if (!promptRef) {
|
|
1284
|
+
throw new Error(`Usage: orizu prompts ${command} <prompt-id-or-name> --project <team/project> [--json]`);
|
|
1285
|
+
}
|
|
1286
|
+
const response = await authedFetch(`/api/cli/prompts/${encodeURIComponent(promptRef)}?project=${encodeURIComponent(project)}`, {
|
|
1287
|
+
method: 'PATCH',
|
|
1288
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1289
|
+
body: JSON.stringify({ archived }),
|
|
1290
|
+
});
|
|
1291
|
+
if (!response.ok) {
|
|
1292
|
+
throw new Error(`Failed to ${command} prompt: ${await response.text()}`);
|
|
1293
|
+
}
|
|
1294
|
+
const data = await parseJsonResponse(response, `Prompt ${command}`);
|
|
1295
|
+
if (hasJsonFlag()) {
|
|
1296
|
+
printJson(data);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
printLine(`${archived ? 'Archived' : 'Restored'} prompt ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)})`);
|
|
1300
|
+
}
|
|
1206
1301
|
async function bindPromptScorer(role) {
|
|
1207
1302
|
const promptId = getPositionalArg(3);
|
|
1208
1303
|
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
@@ -1400,6 +1495,10 @@ async function submitRunResults() {
|
|
|
1400
1495
|
throw new Error(`Failed to submit run: ${await response.text()}`);
|
|
1401
1496
|
}
|
|
1402
1497
|
const data = await parseJsonResponse(response, 'Run submit');
|
|
1498
|
+
if (hasJsonFlag()) {
|
|
1499
|
+
printJson({ run: data.run });
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1403
1502
|
printLine(`Submitted run ${sanitizeTerminalText(data.run.id)} ` +
|
|
1404
1503
|
`(${formatPercent(data.run.aggregateScore)}) -> ` +
|
|
1405
1504
|
sanitizeTerminalText(data.run.perRowResultsStoragePath));
|
|
@@ -1857,9 +1956,17 @@ async function runGepaOptimization() {
|
|
|
1857
1956
|
if (result.status !== 0) {
|
|
1858
1957
|
throw new Error(`orizu-gepa failed with exit code ${result.status}`);
|
|
1859
1958
|
}
|
|
1959
|
+
if (hasJsonFlag()) {
|
|
1960
|
+
printJson({ status: 'completed', exitCode: 0 });
|
|
1961
|
+
}
|
|
1860
1962
|
}
|
|
1861
1963
|
async function listTeams() {
|
|
1862
|
-
|
|
1964
|
+
const teams = await fetchTeams();
|
|
1965
|
+
if (hasJsonFlag()) {
|
|
1966
|
+
printJson({ teams });
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
printTeams(teams);
|
|
1863
1970
|
}
|
|
1864
1971
|
async function resolveTeamSlug(teamSlugArg) {
|
|
1865
1972
|
if (teamSlugArg) {
|
|
@@ -1891,12 +1998,21 @@ async function createTeam() {
|
|
|
1891
1998
|
throw new Error(`Failed to create team: ${await response.text()}`);
|
|
1892
1999
|
}
|
|
1893
2000
|
const data = await parseJsonResponse(response, 'Team create');
|
|
2001
|
+
if (hasJsonFlag()) {
|
|
2002
|
+
printJson({ team: data.team });
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
1894
2005
|
printLine(`Created team: ${sanitizeTerminalText(data.team.name)} (${sanitizeTerminalText(data.team.slug)})`);
|
|
1895
2006
|
}
|
|
1896
2007
|
async function listProjects() {
|
|
1897
2008
|
const teamSlugArg = getArg('--team');
|
|
1898
2009
|
const teamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null;
|
|
1899
|
-
|
|
2010
|
+
const projects = await fetchProjects(teamSlug || undefined);
|
|
2011
|
+
if (hasJsonFlag()) {
|
|
2012
|
+
printJson({ projects });
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
printProjects(projects);
|
|
1900
2016
|
}
|
|
1901
2017
|
async function createProject() {
|
|
1902
2018
|
const name = getArg('--name');
|
|
@@ -1918,15 +2034,29 @@ async function createProject() {
|
|
|
1918
2034
|
throw new Error(`Failed to create project: ${await response.text()}`);
|
|
1919
2035
|
}
|
|
1920
2036
|
const data = await parseJsonResponse(response, 'Project create');
|
|
2037
|
+
if (hasJsonFlag()) {
|
|
2038
|
+
printJson({ project: data.project });
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
1921
2041
|
printLine(`Created project ${sanitizeTerminalText(`${data.project.teamSlug}/${data.project.slug}`)}`);
|
|
1922
2042
|
}
|
|
1923
2043
|
async function listTasks() {
|
|
1924
2044
|
const project = getArg('--project');
|
|
1925
|
-
|
|
2045
|
+
const tasks = await fetchTasks(project || undefined);
|
|
2046
|
+
if (hasJsonFlag()) {
|
|
2047
|
+
printJson({ tasks });
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
printTasks(tasks);
|
|
1926
2051
|
}
|
|
1927
2052
|
async function listApps() {
|
|
1928
2053
|
const project = getArg('--project') || await resolveProjectSlug(null);
|
|
1929
|
-
|
|
2054
|
+
const apps = await fetchApps(project);
|
|
2055
|
+
if (hasJsonFlag()) {
|
|
2056
|
+
printJson({ apps });
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
printApps(apps);
|
|
1930
2060
|
}
|
|
1931
2061
|
function readSourceFile(pathArg) {
|
|
1932
2062
|
const expandedPath = expandHomePath(pathArg);
|
|
@@ -1986,24 +2116,82 @@ async function askYesNo(question, defaultYes) {
|
|
|
1986
2116
|
rl.close();
|
|
1987
2117
|
}
|
|
1988
2118
|
}
|
|
1989
|
-
|
|
2119
|
+
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
|
+
function describeSkillTarget(target) {
|
|
2121
|
+
if (target === 'claude-user')
|
|
2122
|
+
return 'Claude Code, for you across all projects';
|
|
2123
|
+
if (target === 'claude-project')
|
|
2124
|
+
return 'Claude Code, only for this project';
|
|
2125
|
+
if (target === 'agent-user')
|
|
2126
|
+
return 'Codex / Open Agent Skills, for you across all projects';
|
|
2127
|
+
if (target === 'agents-project')
|
|
2128
|
+
return 'Codex / Open Agent Skills, only for this project';
|
|
2129
|
+
if (target === 'codex-project')
|
|
2130
|
+
return 'Codex legacy project folder (.codex/skills)';
|
|
2131
|
+
return 'Project instructions, managed Orizu section in AGENTS.md';
|
|
2132
|
+
}
|
|
2133
|
+
async function promptSkillInstallTargets(scope) {
|
|
1990
2134
|
if (!isInteractiveTerminal()) {
|
|
1991
|
-
throw new Error(
|
|
2135
|
+
throw new Error(SKILL_INSTALL_USAGE);
|
|
1992
2136
|
}
|
|
1993
|
-
|
|
2137
|
+
const choices = [
|
|
2138
|
+
{ label: describeSkillTarget(getTargetForAgent('claude', scope)), target: getTargetForAgent('claude', scope), defaultYes: true },
|
|
2139
|
+
{ label: describeSkillTarget(getTargetForAgent('codex', scope)), target: getTargetForAgent('codex', scope), defaultYes: true },
|
|
2140
|
+
{ label: describeSkillTarget('agents-md'), target: 'agents-md', defaultYes: false },
|
|
2141
|
+
];
|
|
2142
|
+
printLine('Where should Orizu be available?');
|
|
1994
2143
|
const targets = [];
|
|
1995
|
-
for (const
|
|
1996
|
-
const
|
|
1997
|
-
if (await askYesNo(`
|
|
1998
|
-
targets.push(target);
|
|
2144
|
+
for (const choice of choices) {
|
|
2145
|
+
const installPath = getSkillInstallPath(choice.target);
|
|
2146
|
+
if (await askYesNo(` ${choice.label}\n ${installPath}?`, choice.defaultYes)) {
|
|
2147
|
+
targets.push(choice.target);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
if (await askYesNo(' Show advanced targets?', false)) {
|
|
2151
|
+
for (const target of SKILL_INSTALL_TARGETS) {
|
|
2152
|
+
if (targets.includes(target)) {
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
const installPath = getSkillInstallPath(target);
|
|
2156
|
+
if (await askYesNo(` ${describeSkillTarget(target)} [${target}]\n ${installPath}?`, false)) {
|
|
2157
|
+
targets.push(target);
|
|
2158
|
+
}
|
|
1999
2159
|
}
|
|
2000
2160
|
}
|
|
2001
2161
|
return targets;
|
|
2002
2162
|
}
|
|
2003
|
-
function
|
|
2004
|
-
const
|
|
2163
|
+
function parseSkillInstallScope() {
|
|
2164
|
+
const raw = getArg('--scope');
|
|
2165
|
+
if (raw === null || raw === 'global') {
|
|
2166
|
+
return 'global';
|
|
2167
|
+
}
|
|
2168
|
+
if (raw === 'project') {
|
|
2169
|
+
return 'local';
|
|
2170
|
+
}
|
|
2171
|
+
throw new Error(`Unknown --scope '${raw}'. Choices: global, project.`);
|
|
2172
|
+
}
|
|
2173
|
+
function parseSkillInstallMode() {
|
|
2174
|
+
const raw = getArg('--mode');
|
|
2175
|
+
if (raw === null) {
|
|
2176
|
+
return 'auto';
|
|
2177
|
+
}
|
|
2178
|
+
if (raw !== 'auto' && raw !== 'link' && raw !== 'copy') {
|
|
2179
|
+
throw new Error(`Unknown --mode '${raw}'. Choices: auto, link, copy.`);
|
|
2180
|
+
}
|
|
2181
|
+
return raw;
|
|
2182
|
+
}
|
|
2183
|
+
function parseSkillInstallTargets(scope) {
|
|
2005
2184
|
const targets = [];
|
|
2006
|
-
for (const
|
|
2185
|
+
for (const agent of getArgs('--agent')) {
|
|
2186
|
+
if (!isSkillInstallAgent(agent)) {
|
|
2187
|
+
throw new Error(`Unknown agent '${agent}'. Available agents: ${SKILL_INSTALL_AGENTS.join(', ')}`);
|
|
2188
|
+
}
|
|
2189
|
+
const target = getTargetForAgent(agent, scope);
|
|
2190
|
+
if (!targets.includes(target)) {
|
|
2191
|
+
targets.push(target);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
for (const target of getArgs('--target')) {
|
|
2007
2195
|
if (!isSkillInstallTarget(target)) {
|
|
2008
2196
|
throw new Error(`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`);
|
|
2009
2197
|
}
|
|
@@ -2022,36 +2210,412 @@ function formatSkillInstallAction(action) {
|
|
|
2022
2210
|
return 'Would write';
|
|
2023
2211
|
return 'Would update';
|
|
2024
2212
|
}
|
|
2025
|
-
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
if (targets.length === 0) {
|
|
2030
|
-
targets = await promptSkillInstallTargets();
|
|
2213
|
+
function describePlannedWrite(target, action, mode, path) {
|
|
2214
|
+
const verb = action.includes('create') || action === 'created' ? 'create' : 'replace';
|
|
2215
|
+
if (target === 'agents-md') {
|
|
2216
|
+
return ` ${path} (${verb} managed Orizu section)`;
|
|
2031
2217
|
}
|
|
2032
|
-
if (
|
|
2033
|
-
|
|
2034
|
-
|
|
2218
|
+
if (mode === 'link') {
|
|
2219
|
+
return ` ${path} (${verb}, symlink to the CLI-managed skill)`;
|
|
2220
|
+
}
|
|
2221
|
+
return ` ${path} (${verb}, full copy with sync metadata)`;
|
|
2222
|
+
}
|
|
2223
|
+
async function applySkillInstallTargets(targets, options) {
|
|
2224
|
+
const cliVersion = getCliVersion();
|
|
2225
|
+
const jsonMode = hasJsonFlag();
|
|
2226
|
+
const planned = targets.map(target => installSkillTarget(target, { dryRun: true, mode: options.mode, cliVersion }));
|
|
2227
|
+
if (!jsonMode) {
|
|
2228
|
+
printLine('Planned writes:');
|
|
2229
|
+
for (const plan of planned) {
|
|
2230
|
+
printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path));
|
|
2231
|
+
}
|
|
2232
|
+
printLine('');
|
|
2035
2233
|
}
|
|
2234
|
+
if (options.dryRun) {
|
|
2235
|
+
if (!jsonMode) {
|
|
2236
|
+
printLine('Dry run: no files were changed.');
|
|
2237
|
+
}
|
|
2238
|
+
return planned.map(plan => ({
|
|
2239
|
+
target: plan.target,
|
|
2240
|
+
path: plan.path,
|
|
2241
|
+
action: 'skipped',
|
|
2242
|
+
mode: plan.mode,
|
|
2243
|
+
}));
|
|
2244
|
+
}
|
|
2245
|
+
const outcomes = [];
|
|
2036
2246
|
for (const target of targets) {
|
|
2037
|
-
let overwrite = skipConfirm;
|
|
2247
|
+
let overwrite = options.skipConfirm;
|
|
2038
2248
|
const installPath = getSkillInstallPath(target);
|
|
2039
|
-
if (!
|
|
2249
|
+
if (!overwrite && targetNeedsOverwrite(target)) {
|
|
2040
2250
|
if (!isInteractiveTerminal()) {
|
|
2041
2251
|
throw new Error(`${installPath} already exists. Pass --yes to replace it.`);
|
|
2042
2252
|
}
|
|
2043
2253
|
overwrite = await askYesNo(`Replace ${installPath}?`, true);
|
|
2044
2254
|
if (!overwrite) {
|
|
2045
|
-
|
|
2255
|
+
if (!jsonMode) {
|
|
2256
|
+
printLine(`Skipped ${installPath}`);
|
|
2257
|
+
}
|
|
2258
|
+
outcomes.push({ target, path: installPath, action: 'skipped', mode: null });
|
|
2046
2259
|
continue;
|
|
2047
2260
|
}
|
|
2048
2261
|
}
|
|
2262
|
+
try {
|
|
2263
|
+
const result = installSkillTarget(target, {
|
|
2264
|
+
overwrite,
|
|
2265
|
+
mode: options.mode,
|
|
2266
|
+
cliVersion,
|
|
2267
|
+
});
|
|
2268
|
+
if (!jsonMode) {
|
|
2269
|
+
const suffix = result.mode === 'link' ? ' (symlink)' : '';
|
|
2270
|
+
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`);
|
|
2271
|
+
}
|
|
2272
|
+
outcomes.push({
|
|
2273
|
+
target,
|
|
2274
|
+
path: result.path,
|
|
2275
|
+
action: result.action,
|
|
2276
|
+
mode: result.mode,
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
catch (error) {
|
|
2280
|
+
const message = error?.message || String(error);
|
|
2281
|
+
if (!jsonMode) {
|
|
2282
|
+
printLine(`Failed ${installPath}: ${message}`);
|
|
2283
|
+
}
|
|
2284
|
+
outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message });
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
return outcomes;
|
|
2288
|
+
}
|
|
2289
|
+
async function installSkillCommand() {
|
|
2290
|
+
const skipConfirm = hasArg('--yes');
|
|
2291
|
+
const dryRun = hasArg('--dry-run');
|
|
2292
|
+
const scope = parseSkillInstallScope();
|
|
2293
|
+
const mode = parseSkillInstallMode();
|
|
2294
|
+
let targets = parseSkillInstallTargets(scope);
|
|
2295
|
+
if (targets.length === 0) {
|
|
2296
|
+
targets = await promptSkillInstallTargets(scope);
|
|
2297
|
+
}
|
|
2298
|
+
if (targets.length === 0) {
|
|
2299
|
+
if (hasJsonFlag()) {
|
|
2300
|
+
printJson({ installs: [] });
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
printLine('No targets selected; nothing to do.');
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
const outcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
|
|
2307
|
+
if (hasJsonFlag()) {
|
|
2308
|
+
printJson({ dryRun, installs: outcomes });
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
function describeSkillTargetState(status) {
|
|
2312
|
+
if (status.state === 'current')
|
|
2313
|
+
return 'current';
|
|
2314
|
+
if (status.state === 'stale')
|
|
2315
|
+
return 'stale (run `orizu skills update`)';
|
|
2316
|
+
if (status.state === 'broken-link')
|
|
2317
|
+
return 'broken symlink (run `orizu skills update`)';
|
|
2318
|
+
if (status.state === 'unmanaged')
|
|
2319
|
+
return 'exists without a managed Orizu section';
|
|
2320
|
+
return 'not installed';
|
|
2321
|
+
}
|
|
2322
|
+
function skillsStatusCommand() {
|
|
2323
|
+
const source = resolveSkillSource();
|
|
2324
|
+
const statuses = SKILL_INSTALL_TARGETS.map(target => getSkillTargetStatus(target));
|
|
2325
|
+
if (hasJsonFlag()) {
|
|
2326
|
+
printJson({
|
|
2327
|
+
name: source.name,
|
|
2328
|
+
root: source.root,
|
|
2329
|
+
source: source.source,
|
|
2330
|
+
cliVersion: getCliVersion(),
|
|
2331
|
+
sourceHash: computeSkillContentHash(source.root),
|
|
2332
|
+
targets: statuses.map(status => ({
|
|
2333
|
+
target: status.target,
|
|
2334
|
+
path: status.path,
|
|
2335
|
+
state: status.state,
|
|
2336
|
+
mode: status.mode,
|
|
2337
|
+
linkTarget: status.linkTarget,
|
|
2338
|
+
installedHash: status.installedHash,
|
|
2339
|
+
meta: status.meta,
|
|
2340
|
+
})),
|
|
2341
|
+
});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
printLine(`Skill source: ${source.root} (${source.source})`);
|
|
2345
|
+
printLine('');
|
|
2346
|
+
for (const status of statuses) {
|
|
2347
|
+
const mode = status.mode ? `, ${status.mode}` : '';
|
|
2348
|
+
printLine(` ${status.target.padEnd(16)} ${describeSkillTargetState(status)}${mode}`);
|
|
2349
|
+
printLine(` ${''.padEnd(16)} ${status.path}`);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async function skillsUpdateCommand() {
|
|
2353
|
+
const dryRun = hasArg('--dry-run');
|
|
2354
|
+
const cliVersion = getCliVersion();
|
|
2355
|
+
const updates = [];
|
|
2356
|
+
for (const target of SKILL_INSTALL_TARGETS) {
|
|
2357
|
+
const status = getSkillTargetStatus(target);
|
|
2358
|
+
if (status.state === 'missing' || status.state === 'unmanaged') {
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
if (status.state === 'current') {
|
|
2362
|
+
updates.push({ target, path: status.path, action: 'already-current' });
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
if (dryRun) {
|
|
2366
|
+
updates.push({ target, path: status.path, action: 'would-update' });
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
const refreshMode = status.mode === 'link' ? 'auto' : 'copy';
|
|
2049
2370
|
const result = installSkillTarget(target, {
|
|
2050
|
-
overwrite,
|
|
2051
|
-
|
|
2371
|
+
overwrite: true,
|
|
2372
|
+
mode: refreshMode,
|
|
2373
|
+
cliVersion,
|
|
2374
|
+
});
|
|
2375
|
+
updates.push({
|
|
2376
|
+
target,
|
|
2377
|
+
path: status.path,
|
|
2378
|
+
action: status.state === 'broken-link' ? 'relinked' : result.action,
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
if (hasJsonFlag()) {
|
|
2382
|
+
printJson({ updates });
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
if (updates.length === 0) {
|
|
2386
|
+
printLine('No Orizu skill installs found. Run `orizu install-skill` first.');
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
for (const update of updates) {
|
|
2390
|
+
if (update.action === 'already-current') {
|
|
2391
|
+
printLine(`Current ${update.path}`);
|
|
2392
|
+
}
|
|
2393
|
+
else if (update.action === 'would-update') {
|
|
2394
|
+
printLine(`Would update ${update.path}`);
|
|
2395
|
+
}
|
|
2396
|
+
else if (update.action === 'relinked') {
|
|
2397
|
+
printLine(`Relinked ${update.path}`);
|
|
2398
|
+
}
|
|
2399
|
+
else {
|
|
2400
|
+
printLine(`Updated ${update.path}`);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
function skillsPathCommand() {
|
|
2405
|
+
const source = resolveSkillSource();
|
|
2406
|
+
if (hasJsonFlag()) {
|
|
2407
|
+
printJson({
|
|
2408
|
+
name: source.name,
|
|
2409
|
+
root: source.root,
|
|
2410
|
+
skillMd: source.skillMd,
|
|
2411
|
+
source: source.source,
|
|
2412
|
+
cliVersion: getCliVersion(),
|
|
2413
|
+
skillHash: computeSkillContentHash(source.root),
|
|
2052
2414
|
});
|
|
2053
|
-
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
printLine(hasArg('--skill-md') ? source.skillMd : source.root);
|
|
2418
|
+
}
|
|
2419
|
+
const LAUNCHABLE_AGENTS = {
|
|
2420
|
+
claude: 'claude',
|
|
2421
|
+
codex: 'codex',
|
|
2422
|
+
};
|
|
2423
|
+
function findExecutable(name) {
|
|
2424
|
+
const pathValue = process.env.PATH || '';
|
|
2425
|
+
for (const dir of pathValue.split(delimiter)) {
|
|
2426
|
+
if (!dir) {
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
const candidate = join(dir, name);
|
|
2430
|
+
if (existsSync(candidate)) {
|
|
2431
|
+
return candidate;
|
|
2432
|
+
}
|
|
2054
2433
|
}
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
function describeSetupAuthState() {
|
|
2437
|
+
const baseUrl = getBaseUrl();
|
|
2438
|
+
return {
|
|
2439
|
+
state: getServerCredentials(baseUrl) ? 'signed-in' : 'signed-out',
|
|
2440
|
+
baseUrl,
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
async function setupCommand() {
|
|
2444
|
+
const dryRun = hasArg('--dry-run');
|
|
2445
|
+
const skipConfirm = hasArg('--yes');
|
|
2446
|
+
const noInput = hasArg('--no-input') || !isInteractiveTerminal();
|
|
2447
|
+
printBannerIfInteractive();
|
|
2448
|
+
printLine('Orizu setup');
|
|
2449
|
+
printLine('');
|
|
2450
|
+
// Phase 1: login
|
|
2451
|
+
printLine('1. Authentication');
|
|
2452
|
+
let auth = describeSetupAuthState();
|
|
2453
|
+
if (hasArg('--skip-login')) {
|
|
2454
|
+
printLine(' Skipped (--skip-login).');
|
|
2455
|
+
}
|
|
2456
|
+
else if (auth.state === 'signed-in') {
|
|
2457
|
+
printLine(` Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`);
|
|
2458
|
+
}
|
|
2459
|
+
else if (noInput || dryRun) {
|
|
2460
|
+
printLine(' Not signed in. Run `orizu login` to authenticate.');
|
|
2461
|
+
}
|
|
2462
|
+
else if (await askYesNo(' Sign in to Orizu now?', true)) {
|
|
2463
|
+
await login();
|
|
2464
|
+
auth = describeSetupAuthState();
|
|
2465
|
+
}
|
|
2466
|
+
else {
|
|
2467
|
+
printLine(' Skipped. Run `orizu login` when ready.');
|
|
2468
|
+
}
|
|
2469
|
+
printLine('');
|
|
2470
|
+
// Phase 2: agent integration
|
|
2471
|
+
printLine('2. Agent integration');
|
|
2472
|
+
const scope = parseSkillInstallScope();
|
|
2473
|
+
const mode = parseSkillInstallMode();
|
|
2474
|
+
let targets = hasArg('--no-install') ? [] : parseSkillInstallTargets(scope);
|
|
2475
|
+
if (targets.length === 0 && !hasArg('--no-install')) {
|
|
2476
|
+
if (noInput) {
|
|
2477
|
+
printLine(' No agents selected. Pass --agent claude and/or --agent codex, or rerun interactively.');
|
|
2478
|
+
}
|
|
2479
|
+
else {
|
|
2480
|
+
targets = await promptSkillInstallTargets(scope);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
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
|
+
printLine('');
|
|
2491
|
+
// Phase 3: local workspace
|
|
2492
|
+
printLine('3. Local workspace');
|
|
2493
|
+
let workspaceState = 'skipped';
|
|
2494
|
+
if (hasArg('--no-workspace')) {
|
|
2495
|
+
printLine(' Skipped (--no-workspace).');
|
|
2496
|
+
}
|
|
2497
|
+
else if (workspaceExists()) {
|
|
2498
|
+
workspaceState = 'exists';
|
|
2499
|
+
printLine(` ${WORKSPACE_DIR_NAME}/ already initialized.`);
|
|
2500
|
+
}
|
|
2501
|
+
else {
|
|
2502
|
+
const wantsWorkspace = hasArg('--workspace') ||
|
|
2503
|
+
(!noInput && await askYesNo(` Create a gitignored ${WORKSPACE_DIR_NAME}/ workspace for local Orizu artifacts?`, true));
|
|
2504
|
+
if (wantsWorkspace) {
|
|
2505
|
+
const result = initOrizuWorkspace({
|
|
2506
|
+
baseUrl: auth.baseUrl,
|
|
2507
|
+
cliVersion: getCliVersion(),
|
|
2508
|
+
dryRun,
|
|
2509
|
+
});
|
|
2510
|
+
workspaceState = result.state === 'would-create' ? 'would-create' : result.state;
|
|
2511
|
+
for (const action of result.actions) {
|
|
2512
|
+
printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
else {
|
|
2516
|
+
printLine(` Skipped. Rerun with --workspace to create ${WORKSPACE_DIR_NAME}/.`);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
printLine('');
|
|
2520
|
+
// Phase 4: coding-agent handoff
|
|
2521
|
+
const handoffPrompt = renderAgentSetupPrompt({
|
|
2522
|
+
workspacePath: workspaceState === 'created' || workspaceState === 'exists'
|
|
2523
|
+
? getWorkspaceRoot()
|
|
2524
|
+
: null,
|
|
2525
|
+
});
|
|
2526
|
+
if (!hasArg('--no-handoff')) {
|
|
2527
|
+
printLine('4. Coding-agent handoff');
|
|
2528
|
+
printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:');
|
|
2529
|
+
printLine('');
|
|
2530
|
+
printLine('--- prompt start ---');
|
|
2531
|
+
printLine(handoffPrompt);
|
|
2532
|
+
printLine('--- prompt end ---');
|
|
2533
|
+
printLine('');
|
|
2534
|
+
const detected = Object.keys(LAUNCHABLE_AGENTS).filter(agent => findExecutable(LAUNCHABLE_AGENTS[agent]));
|
|
2535
|
+
if (detected.length > 0) {
|
|
2536
|
+
printLine(` Detected agents: ${detected.join(', ')}. Launch one with the prompt, e.g.:`);
|
|
2537
|
+
for (const agent of detected) {
|
|
2538
|
+
printLine(` ${LAUNCHABLE_AGENTS[agent]} "$(orizu setup prompt)"`);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
const launchAgent = getArg('--launch');
|
|
2542
|
+
if (launchAgent) {
|
|
2543
|
+
if (!(launchAgent in LAUNCHABLE_AGENTS)) {
|
|
2544
|
+
throw new Error(`Unknown --launch agent '${launchAgent}'. Choices: ${Object.keys(LAUNCHABLE_AGENTS).join(', ')}.`);
|
|
2545
|
+
}
|
|
2546
|
+
const binary = findExecutable(LAUNCHABLE_AGENTS[launchAgent]);
|
|
2547
|
+
if (!isInteractiveTerminal()) {
|
|
2548
|
+
printLine(' Not launching: --launch requires an interactive terminal.');
|
|
2549
|
+
}
|
|
2550
|
+
else if (!binary) {
|
|
2551
|
+
printLine(` Not launching: '${LAUNCHABLE_AGENTS[launchAgent]}' was not found on PATH.`);
|
|
2552
|
+
}
|
|
2553
|
+
else if (dryRun) {
|
|
2554
|
+
printLine(` Would launch ${binary} with the setup prompt.`);
|
|
2555
|
+
}
|
|
2556
|
+
else if (skipConfirm || await askYesNo(` Launch ${LAUNCHABLE_AGENTS[launchAgent]} with the setup prompt now?`, true)) {
|
|
2557
|
+
printLine(` Launching ${binary}…`);
|
|
2558
|
+
spawnSync(binary, [handoffPrompt], { stdio: 'inherit' });
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
printLine('');
|
|
2562
|
+
}
|
|
2563
|
+
// Setup summary (success criteria checklist)
|
|
2564
|
+
const summaryTargets = installOutcomes.length > 0
|
|
2565
|
+
? installOutcomes
|
|
2566
|
+
.filter(outcome => outcome.action !== 'failed')
|
|
2567
|
+
.map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
|
|
2568
|
+
: [];
|
|
2569
|
+
const source = resolveSkillSource();
|
|
2570
|
+
if (hasJsonFlag()) {
|
|
2571
|
+
printJson({
|
|
2572
|
+
auth: { state: auth.state, server: auth.baseUrl },
|
|
2573
|
+
integrations: installOutcomes.map(outcome => ({
|
|
2574
|
+
target: outcome.target,
|
|
2575
|
+
path: outcome.path,
|
|
2576
|
+
action: outcome.action,
|
|
2577
|
+
mode: outcome.mode,
|
|
2578
|
+
error: outcome.error,
|
|
2579
|
+
})),
|
|
2580
|
+
skill: {
|
|
2581
|
+
root: source.root,
|
|
2582
|
+
source: source.source,
|
|
2583
|
+
cliVersion: getCliVersion(),
|
|
2584
|
+
skillHash: computeSkillContentHash(source.root),
|
|
2585
|
+
},
|
|
2586
|
+
workspace: { state: workspaceState, path: getWorkspaceRoot() },
|
|
2587
|
+
});
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
printLine('Setup summary');
|
|
2591
|
+
printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`);
|
|
2592
|
+
if (summaryTargets.length > 0) {
|
|
2593
|
+
printLine(' Integrations:');
|
|
2594
|
+
for (const { outcome, status } of summaryTargets) {
|
|
2595
|
+
const modeLabel = status.mode ? `, ${status.mode}` : '';
|
|
2596
|
+
printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
else {
|
|
2600
|
+
printLine(' Integrations: none installed this run — `orizu install-skill` sets them up.');
|
|
2601
|
+
}
|
|
2602
|
+
const failed = installOutcomes.filter(outcome => outcome.action === 'failed');
|
|
2603
|
+
for (const failure of failed) {
|
|
2604
|
+
printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`);
|
|
2605
|
+
}
|
|
2606
|
+
printLine(` Skill source: ${source.root} (${source.source})`);
|
|
2607
|
+
printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : `${getWorkspaceRoot()} (${workspaceState})`}`);
|
|
2608
|
+
printLine(' Next: hand the prompt above to your coding agent to plan repo-specific Orizu adoption.');
|
|
2609
|
+
}
|
|
2610
|
+
function setupPromptCommand() {
|
|
2611
|
+
const prompt = renderAgentSetupPrompt({
|
|
2612
|
+
workspacePath: workspaceExists() ? getWorkspaceRoot() : null,
|
|
2613
|
+
});
|
|
2614
|
+
if (hasJsonFlag()) {
|
|
2615
|
+
printJson({ prompt });
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
printLine(prompt);
|
|
2055
2619
|
}
|
|
2056
2620
|
function readJsonFile(pathArg) {
|
|
2057
2621
|
const raw = readSourceFile(pathArg);
|
|
@@ -2281,6 +2845,10 @@ async function createAppFromFile() {
|
|
|
2281
2845
|
throw new Error(`Failed to create app: ${await response.text()}`);
|
|
2282
2846
|
}
|
|
2283
2847
|
const data = await parseJsonResponse(response, 'App create');
|
|
2848
|
+
if (hasJsonFlag()) {
|
|
2849
|
+
printJson({ app: data.app, warnings: data.warnings || [] });
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2284
2852
|
printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`);
|
|
2285
2853
|
if (data.app.url) {
|
|
2286
2854
|
printLine(`View app: ${formatTerminalLink(data.app.url)}`);
|
|
@@ -2316,6 +2884,14 @@ async function previewAppFromFile() {
|
|
|
2316
2884
|
headed,
|
|
2317
2885
|
keepOpen,
|
|
2318
2886
|
});
|
|
2887
|
+
if (hasJsonFlag()) {
|
|
2888
|
+
printJson({
|
|
2889
|
+
url: result.url,
|
|
2890
|
+
screenshotPath: result.screenshotPath || null,
|
|
2891
|
+
warnings: result.warnings,
|
|
2892
|
+
});
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2319
2895
|
printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
|
|
2320
2896
|
if (result.screenshotPath) {
|
|
2321
2897
|
printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
|
|
@@ -2358,6 +2934,10 @@ async function updateAppFromFile() {
|
|
|
2358
2934
|
throw new Error(`Failed to update app: ${await response.text()}`);
|
|
2359
2935
|
}
|
|
2360
2936
|
const data = await parseJsonResponse(response, 'App update');
|
|
2937
|
+
if (hasJsonFlag()) {
|
|
2938
|
+
printJson({ app: data.app, warnings: data.warnings || [] });
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2361
2941
|
printLine(`Updated app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) to v${data.app.versionNum}`);
|
|
2362
2942
|
if (data.warnings?.length) {
|
|
2363
2943
|
printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`);
|
|
@@ -2392,6 +2972,10 @@ async function linkAppDataset() {
|
|
|
2392
2972
|
throw new Error(`Failed to link dataset: ${await response.text()}`);
|
|
2393
2973
|
}
|
|
2394
2974
|
const data = await parseJsonResponse(response, 'App link dataset');
|
|
2975
|
+
if (hasJsonFlag()) {
|
|
2976
|
+
printJson({ app: data.app, linkedDataset: data.linkedDataset, versionNum: data.versionNum });
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2395
2979
|
printLine(`Linked dataset ${sanitizeTerminalText(data.linkedDataset.name)} (${sanitizeTerminalText(data.linkedDataset.id)}) to app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) version ${data.versionNum}`);
|
|
2396
2980
|
}
|
|
2397
2981
|
function parseCommaSeparated(value) {
|
|
@@ -2403,43 +2987,69 @@ function parseCommaSeparated(value) {
|
|
|
2403
2987
|
.map(item => item.trim())
|
|
2404
2988
|
.filter(Boolean);
|
|
2405
2989
|
}
|
|
2990
|
+
function readAssignmentManifest(path) {
|
|
2991
|
+
if (!path) {
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
const content = readFileSync(expandHomePath(path), 'utf8');
|
|
2995
|
+
return parseAssignmentManifestJsonl(content);
|
|
2996
|
+
}
|
|
2406
2997
|
async function createTask() {
|
|
2407
2998
|
const projectSlug = getArg('--project');
|
|
2408
2999
|
const datasetId = getArg('--dataset');
|
|
2409
3000
|
const appId = getArg('--app');
|
|
2410
3001
|
const title = getArg('--title');
|
|
2411
3002
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
3003
|
+
const assignmentFile = getArg('--assignment-file');
|
|
2412
3004
|
const publish = hasArg('--publish');
|
|
2413
3005
|
const versionArg = getArg('--version');
|
|
2414
3006
|
const instructions = getArg('--instructions');
|
|
2415
3007
|
const labelsPerItemArg = getArg('--labels-per-item');
|
|
2416
|
-
const
|
|
3008
|
+
const parsedLabelsPerItem = labelsPerItemArg ? Number(labelsPerItemArg) : 1;
|
|
3009
|
+
const labelsPerItem = Number.isInteger(parsedLabelsPerItem) && parsedLabelsPerItem > 0
|
|
3010
|
+
? parsedLabelsPerItem
|
|
3011
|
+
: null;
|
|
2417
3012
|
const parsedVersionNum = versionArg ? Number(versionArg) : Number.NaN;
|
|
2418
3013
|
const versionNum = Number.isInteger(parsedVersionNum) && parsedVersionNum > 0 ? parsedVersionNum : null;
|
|
2419
|
-
if (
|
|
2420
|
-
throw new Error('
|
|
3014
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3015
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3016
|
+
}
|
|
3017
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3018
|
+
if (!projectSlug || !datasetId || !appId || !title || (publish && assignees.length === 0 && !explicitAssignments)) {
|
|
3019
|
+
throw new Error('Usage: orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--publish] [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]');
|
|
2421
3020
|
}
|
|
2422
3021
|
if (versionArg && versionNum === null) {
|
|
2423
3022
|
throw new Error('--version must be a positive integer');
|
|
2424
3023
|
}
|
|
3024
|
+
if (labelsPerItemArg && labelsPerItem === null) {
|
|
3025
|
+
throw new Error('--labels-per-item must be a positive integer');
|
|
3026
|
+
}
|
|
3027
|
+
const requestBody = {
|
|
3028
|
+
projectSlug,
|
|
3029
|
+
datasetId,
|
|
3030
|
+
appId,
|
|
3031
|
+
versionNum,
|
|
3032
|
+
title,
|
|
3033
|
+
publish,
|
|
3034
|
+
instructions,
|
|
3035
|
+
};
|
|
3036
|
+
if (explicitAssignments) {
|
|
3037
|
+
requestBody.explicitAssignments = explicitAssignments;
|
|
3038
|
+
}
|
|
3039
|
+
else {
|
|
3040
|
+
requestBody.memberIds = assignees;
|
|
3041
|
+
}
|
|
3042
|
+
if (!explicitAssignments || labelsPerItemArg) {
|
|
3043
|
+
requestBody.requiredAssignmentsPerRow = labelsPerItem;
|
|
3044
|
+
}
|
|
2425
3045
|
const response = await authedFetch('/api/cli/tasks', {
|
|
2426
3046
|
method: 'POST',
|
|
2427
3047
|
headers: { 'Content-Type': 'application/json' },
|
|
2428
|
-
body: JSON.stringify(
|
|
2429
|
-
projectSlug,
|
|
2430
|
-
datasetId,
|
|
2431
|
-
appId,
|
|
2432
|
-
versionNum,
|
|
2433
|
-
title,
|
|
2434
|
-
memberIds: assignees,
|
|
2435
|
-
publish,
|
|
2436
|
-
instructions,
|
|
2437
|
-
requiredAssignmentsPerRow: labelsPerItem,
|
|
2438
|
-
}),
|
|
3048
|
+
body: JSON.stringify(requestBody),
|
|
2439
3049
|
});
|
|
2440
3050
|
if (!response.ok) {
|
|
2441
3051
|
const cliError = await formatTaskCreateError(response);
|
|
2442
|
-
if (
|
|
3052
|
+
if (hasJsonFlag()) {
|
|
2443
3053
|
const payload = cliError.structuredPayload ?? { error: cliError.message };
|
|
2444
3054
|
printLine(JSON.stringify({
|
|
2445
3055
|
...payload,
|
|
@@ -2449,7 +3059,7 @@ async function createTask() {
|
|
|
2449
3059
|
throw cliError;
|
|
2450
3060
|
}
|
|
2451
3061
|
const data = await parseJsonResponse(response, 'Task create');
|
|
2452
|
-
if (
|
|
3062
|
+
if (hasJsonFlag()) {
|
|
2453
3063
|
printLine(JSON.stringify({
|
|
2454
3064
|
taskId: data.task.id,
|
|
2455
3065
|
datasetId,
|
|
@@ -2458,13 +3068,16 @@ async function createTask() {
|
|
|
2458
3068
|
taskUrl: `${getBaseUrl()}/d/${projectSlug}/tasks/${data.task.id}`,
|
|
2459
3069
|
title: data.task.title,
|
|
2460
3070
|
status: data.task.status,
|
|
3071
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
2461
3072
|
requiredAssignmentsPerRow: data.task.requiredAssignmentsPerRow,
|
|
2462
3073
|
assignmentsCreated: data.assignmentsCreated,
|
|
2463
3074
|
draft: data.task.status === 'draft',
|
|
2464
3075
|
...(data.task.status === 'draft'
|
|
2465
3076
|
? {
|
|
2466
3077
|
message: 'Draft created. Test it manually before assigning.',
|
|
2467
|
-
publishCommand:
|
|
3078
|
+
publishCommand: explicitAssignments
|
|
3079
|
+
? `orizu tasks publish --task ${data.task.id} --assignment-file ${assignmentFile ? shellQuote(assignmentFile) : '<path>'}`
|
|
3080
|
+
: `orizu tasks publish --task ${data.task.id} --assignees <userId1,userId2>`,
|
|
2468
3081
|
}
|
|
2469
3082
|
: {}),
|
|
2470
3083
|
...(data.assignmentShortfall !== undefined ? { assignmentShortfall: data.assignmentShortfall } : {}),
|
|
@@ -2479,33 +3092,44 @@ async function createTask() {
|
|
|
2479
3092
|
`\n Task ID: ${sanitizeTerminalText(data.task.id)}` +
|
|
2480
3093
|
`\n Dataset ID: ${sanitizeTerminalText(datasetId)}` +
|
|
2481
3094
|
`\n Version: v${data.task.versionNum} (${sanitizeTerminalText(data.task.versionId)})` +
|
|
3095
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
2482
3096
|
`\n Labels/row: ${data.task.requiredAssignmentsPerRow}` +
|
|
2483
3097
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
2484
3098
|
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : '') +
|
|
2485
3099
|
`\n URL: ${sanitizeTerminalText(taskUrl)}` +
|
|
2486
3100
|
(isDraft
|
|
2487
|
-
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval:
|
|
3101
|
+
? `\n\nThis task is a draft. Test it manually before assigning.\nPublish after approval: ${explicitAssignments
|
|
3102
|
+
? `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignment-file ${sanitizeTerminalText(assignmentFile || '<path>')}`
|
|
3103
|
+
: `orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`}`
|
|
2488
3104
|
: ''));
|
|
2489
3105
|
}
|
|
2490
3106
|
async function publishTask() {
|
|
2491
3107
|
const taskId = getArg('--task');
|
|
2492
3108
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
2493
|
-
|
|
2494
|
-
|
|
3109
|
+
const assignmentFile = getArg('--assignment-file');
|
|
3110
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3111
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3112
|
+
}
|
|
3113
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3114
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
3115
|
+
throw new Error('Usage: orizu tasks publish --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--json]');
|
|
2495
3116
|
}
|
|
2496
3117
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/publish`, {
|
|
2497
3118
|
method: 'POST',
|
|
2498
3119
|
headers: { 'Content-Type': 'application/json' },
|
|
2499
|
-
body: JSON.stringify(
|
|
3120
|
+
body: JSON.stringify(explicitAssignments
|
|
3121
|
+
? { explicitAssignments }
|
|
3122
|
+
: { memberIds: assignees }),
|
|
2500
3123
|
});
|
|
2501
3124
|
if (!response.ok) {
|
|
2502
3125
|
throw new Error(`Failed to publish task: ${await extractErrorMessage(response)}`);
|
|
2503
3126
|
}
|
|
2504
3127
|
const data = await parseJsonResponse(response, 'Task publish');
|
|
2505
|
-
if (
|
|
3128
|
+
if (hasJsonFlag()) {
|
|
2506
3129
|
printLine(JSON.stringify({
|
|
2507
3130
|
taskId: data.task.id,
|
|
2508
3131
|
status: data.task.status,
|
|
3132
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
2509
3133
|
assignmentsCreated: data.assignmentsCreated,
|
|
2510
3134
|
assignmentsProcessed: data.assignmentsProcessed,
|
|
2511
3135
|
...(data.assignmentsRemoved !== undefined
|
|
@@ -2519,6 +3143,7 @@ async function publishTask() {
|
|
|
2519
3143
|
return;
|
|
2520
3144
|
}
|
|
2521
3145
|
printLine(`Published task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]` +
|
|
3146
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
2522
3147
|
`\n Assignments: ${data.assignmentsCreated}` +
|
|
2523
3148
|
(data.assignmentsRemoved !== undefined
|
|
2524
3149
|
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
@@ -2528,19 +3153,50 @@ async function publishTask() {
|
|
|
2528
3153
|
async function assignTask() {
|
|
2529
3154
|
const taskId = getArg('--task');
|
|
2530
3155
|
const assignees = parseCommaSeparated(getArg('--assignees'));
|
|
2531
|
-
|
|
2532
|
-
|
|
3156
|
+
const assignmentFile = getArg('--assignment-file');
|
|
3157
|
+
if (assignmentFile && assignees.length > 0) {
|
|
3158
|
+
throw new Error('Use either --assignment-file or --assignees, not both.');
|
|
3159
|
+
}
|
|
3160
|
+
const explicitAssignments = readAssignmentManifest(assignmentFile);
|
|
3161
|
+
if (!taskId || (assignees.length === 0 && !explicitAssignments)) {
|
|
3162
|
+
throw new Error('Usage: orizu tasks assign --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--replace-existing] [--json]');
|
|
3163
|
+
}
|
|
3164
|
+
const requestBody = explicitAssignments
|
|
3165
|
+
? { explicitAssignments }
|
|
3166
|
+
: { memberIds: assignees };
|
|
3167
|
+
if (hasArg('--replace-existing')) {
|
|
3168
|
+
requestBody.replaceExisting = true;
|
|
2533
3169
|
}
|
|
2534
3170
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/assign`, {
|
|
2535
3171
|
method: 'POST',
|
|
2536
3172
|
headers: { 'Content-Type': 'application/json' },
|
|
2537
|
-
body: JSON.stringify(
|
|
3173
|
+
body: JSON.stringify(requestBody),
|
|
2538
3174
|
});
|
|
2539
3175
|
if (!response.ok) {
|
|
2540
3176
|
throw new Error(`Failed to assign task: ${await response.text()}`);
|
|
2541
3177
|
}
|
|
2542
3178
|
const data = await parseJsonResponse(response, 'Task assign');
|
|
2543
|
-
|
|
3179
|
+
if (hasJsonFlag()) {
|
|
3180
|
+
printJson({
|
|
3181
|
+
assignmentMode: explicitAssignments ? 'custom' : 'auto',
|
|
3182
|
+
assignmentsProcessed: data.assignmentsProcessed ?? data.assignmentsCreated,
|
|
3183
|
+
assignmentsCreated: data.assignmentsCreated,
|
|
3184
|
+
...(data.assignmentsRemoved !== undefined
|
|
3185
|
+
? { assignmentsRemoved: data.assignmentsRemoved }
|
|
3186
|
+
: {}),
|
|
3187
|
+
...(data.assignmentShortfall !== undefined
|
|
3188
|
+
? { assignmentShortfall: data.assignmentShortfall }
|
|
3189
|
+
: {}),
|
|
3190
|
+
...(data.warning ? { warning: data.warning } : {}),
|
|
3191
|
+
});
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
printLine(`Created ${data.assignmentsCreated} assignments.` +
|
|
3195
|
+
`\n Mode: ${explicitAssignments ? 'custom row map' : 'auto distribute'}` +
|
|
3196
|
+
(data.assignmentsRemoved !== undefined
|
|
3197
|
+
? `\n Replaced: ${data.assignmentsRemoved} old assignments removed`
|
|
3198
|
+
: '') +
|
|
3199
|
+
(data.warning ? `\n Warning: ${sanitizeTerminalText(data.warning)}` : ''));
|
|
2544
3200
|
}
|
|
2545
3201
|
async function taskStatus() {
|
|
2546
3202
|
const taskId = getArg('--task');
|
|
@@ -2550,7 +3206,7 @@ async function taskStatus() {
|
|
|
2550
3206
|
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`);
|
|
2551
3207
|
if (!response.ok) {
|
|
2552
3208
|
const rawBody = await response.text();
|
|
2553
|
-
if (
|
|
3209
|
+
if (hasJsonFlag()) {
|
|
2554
3210
|
let errorPayload = { error: rawBody };
|
|
2555
3211
|
try {
|
|
2556
3212
|
const parsed = JSON.parse(rawBody);
|
|
@@ -2577,7 +3233,7 @@ async function taskStatus() {
|
|
|
2577
3233
|
throw new Error(`Failed to fetch task status: ${errorMsg}`);
|
|
2578
3234
|
}
|
|
2579
3235
|
const data = await parseJsonResponse(response, 'Task status');
|
|
2580
|
-
if (
|
|
3236
|
+
if (hasJsonFlag()) {
|
|
2581
3237
|
printLine(JSON.stringify(data, null, 2));
|
|
2582
3238
|
return;
|
|
2583
3239
|
}
|
|
@@ -2599,6 +3255,10 @@ async function updateTaskStatus(targetStatus) {
|
|
|
2599
3255
|
throw new Error(`Failed to ${verb} task: ${await response.text()}`);
|
|
2600
3256
|
}
|
|
2601
3257
|
const data = await parseJsonResponse(response, 'Task status update');
|
|
3258
|
+
if (hasJsonFlag()) {
|
|
3259
|
+
printJson({ task: data.task });
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
2602
3262
|
const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused';
|
|
2603
3263
|
printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
|
|
2604
3264
|
}
|
|
@@ -2656,7 +3316,7 @@ async function appDetail() {
|
|
|
2656
3316
|
if (!detail) {
|
|
2657
3317
|
throw new Error(`App '${appId}' not found in project '${projectSlug}'`);
|
|
2658
3318
|
}
|
|
2659
|
-
if (
|
|
3319
|
+
if (hasJsonFlag()) {
|
|
2660
3320
|
printLine(JSON.stringify(detail, null, 2));
|
|
2661
3321
|
return;
|
|
2662
3322
|
}
|
|
@@ -2709,11 +3369,24 @@ async function exportAppSource() {
|
|
|
2709
3369
|
? expandHomePath(outPathArg)
|
|
2710
3370
|
: defaultAppExportFilename(data.app.name, data.version.versionNum);
|
|
2711
3371
|
writeTextFileEnsuringDir(filename, data.version.code);
|
|
3372
|
+
if (hasJsonFlag()) {
|
|
3373
|
+
printJson({
|
|
3374
|
+
app: { id: data.app.id, name: data.app.name },
|
|
3375
|
+
versionNum: data.version.versionNum,
|
|
3376
|
+
savedTo: filename,
|
|
3377
|
+
});
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
2712
3380
|
printLine(`Saved app ${sanitizeTerminalText(data.app.name)} v${data.version.versionNum} source to ${sanitizeTerminalText(filename)}`);
|
|
2713
3381
|
}
|
|
2714
3382
|
async function listTeamMembers() {
|
|
2715
3383
|
const teamSlug = await resolveTeamSlug(getArg('--team'));
|
|
2716
|
-
|
|
3384
|
+
const members = await fetchTeamMembers(teamSlug);
|
|
3385
|
+
if (hasJsonFlag()) {
|
|
3386
|
+
printJson({ team: teamSlug, members });
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
printTeamMembers(members);
|
|
2717
3390
|
}
|
|
2718
3391
|
async function addTeamMember() {
|
|
2719
3392
|
const teamSlug = await resolveTeamSlug(getArg('--team'));
|
|
@@ -2730,6 +3403,10 @@ async function addTeamMember() {
|
|
|
2730
3403
|
throw new Error(`Failed to add team member: ${await response.text()}`);
|
|
2731
3404
|
}
|
|
2732
3405
|
const data = await parseJsonResponse(response, 'Team member add');
|
|
3406
|
+
if (hasJsonFlag()) {
|
|
3407
|
+
printJson({ member: data.member });
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
2733
3410
|
printLine(`Added team member ${sanitizeTerminalText(data.member.email)} (${sanitizeTerminalText(data.member.id)})`);
|
|
2734
3411
|
}
|
|
2735
3412
|
async function removeTeamMember() {
|
|
@@ -2747,6 +3424,10 @@ async function removeTeamMember() {
|
|
|
2747
3424
|
if (!response.ok) {
|
|
2748
3425
|
throw new Error(`Failed to remove team member: ${await response.text()}`);
|
|
2749
3426
|
}
|
|
3427
|
+
if (hasJsonFlag()) {
|
|
3428
|
+
printJson({ removed: { id: member.id, email: member.email } });
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
2750
3431
|
printLine(`Removed team member ${sanitizeTerminalText(member.email)}`);
|
|
2751
3432
|
}
|
|
2752
3433
|
async function changeTeamMemberRole() {
|
|
@@ -2773,6 +3454,10 @@ async function changeTeamMemberRole() {
|
|
|
2773
3454
|
if (!response.ok) {
|
|
2774
3455
|
throw new Error(`Failed to update member role: ${await response.text()}`);
|
|
2775
3456
|
}
|
|
3457
|
+
if (hasJsonFlag()) {
|
|
3458
|
+
printJson({ member: { id: member.id, email: member.email, role } });
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
2776
3461
|
printLine(`Updated ${sanitizeTerminalText(member.email)} role to ${sanitizeTerminalText(role)}`);
|
|
2777
3462
|
}
|
|
2778
3463
|
async function createDatasetFromRows(project, name, sourceType, rows, readmeMarkdown = null) {
|
|
@@ -2849,6 +3534,10 @@ async function uploadJsonlDatasetInChunks(file, project, datasetName, readmeMark
|
|
|
2849
3534
|
if (!dataset) {
|
|
2850
3535
|
throw new Error('Dataset file contains no rows');
|
|
2851
3536
|
}
|
|
3537
|
+
if (hasJsonFlag()) {
|
|
3538
|
+
printJson({ dataset });
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
2852
3541
|
printLine(`Uploaded dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) with ${dataset.rowCount} rows.`);
|
|
2853
3542
|
if (dataset.url) {
|
|
2854
3543
|
printLine(`View dataset: ${formatTerminalLink(dataset.url)}`);
|
|
@@ -2872,6 +3561,10 @@ async function uploadDataset() {
|
|
|
2872
3561
|
}
|
|
2873
3562
|
const { rows, sourceType } = parseDatasetFile(file);
|
|
2874
3563
|
const data = await createDatasetFromRows(project, datasetName, sourceType, rows, readmeMarkdown);
|
|
3564
|
+
if (hasJsonFlag()) {
|
|
3565
|
+
printJson({ dataset: data.dataset });
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
2875
3568
|
printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`);
|
|
2876
3569
|
if (data.dataset.url) {
|
|
2877
3570
|
printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`);
|
|
@@ -2976,6 +3669,10 @@ async function downloadDataset() {
|
|
|
2976
3669
|
: `${datasetId}.${format}`;
|
|
2977
3670
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
2978
3671
|
writeFileSync(filename, bytes);
|
|
3672
|
+
if (hasJsonFlag()) {
|
|
3673
|
+
printJson({ datasetId, format, savedTo: filename });
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
2979
3676
|
printLine(`Saved dataset ${sanitizeTerminalText(datasetId)} (${format.toUpperCase()}) to ${sanitizeTerminalText(filename)}`);
|
|
2980
3677
|
}
|
|
2981
3678
|
const MAX_INPUT_FILE_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
@@ -3101,6 +3798,10 @@ async function appendDatasetRows() {
|
|
|
3101
3798
|
}
|
|
3102
3799
|
}
|
|
3103
3800
|
if (lastResult) {
|
|
3801
|
+
if (hasJsonFlag()) {
|
|
3802
|
+
printJson({ dataset: lastResult.dataset, appendedCount: totalAppended });
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3104
3805
|
printLine(`Appended ${totalAppended} rows to dataset ${sanitizeTerminalText(lastResult.dataset.name)} (${sanitizeTerminalText(lastResult.dataset.id)}). New row count: ${lastResult.dataset.rowCount}`);
|
|
3105
3806
|
}
|
|
3106
3807
|
}
|
|
@@ -3147,6 +3848,10 @@ async function editDatasetRows() {
|
|
|
3147
3848
|
throw new Error(`Edit rows failed: ${await response.text()}`);
|
|
3148
3849
|
}
|
|
3149
3850
|
const data = await parseJsonResponse(response, 'Dataset edit rows');
|
|
3851
|
+
if (hasJsonFlag()) {
|
|
3852
|
+
printJson({ dataset: data.dataset, updatedCount: data.updatedCount });
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3150
3855
|
printLine(`Updated ${data.updatedCount} rows in dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Current row count: ${data.dataset.rowCount}`);
|
|
3151
3856
|
}
|
|
3152
3857
|
async function deleteDatasetRows() {
|
|
@@ -3175,6 +3880,10 @@ async function deleteDatasetRows() {
|
|
|
3175
3880
|
throw new Error(`Delete rows failed: ${await response.text()}`);
|
|
3176
3881
|
}
|
|
3177
3882
|
const data = await parseJsonResponse(response, 'Dataset delete rows');
|
|
3883
|
+
if (hasJsonFlag()) {
|
|
3884
|
+
printJson({ dataset: data.dataset, deletedCount: data.deletedCount });
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3178
3887
|
printLine(`Deleted ${data.deletedCount} rows from dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`);
|
|
3179
3888
|
}
|
|
3180
3889
|
async function confirmDatasetDeletion(dataset) {
|
|
@@ -3213,6 +3922,10 @@ async function deleteDataset() {
|
|
|
3213
3922
|
throw new Error(`Delete failed: ${await response.text()}`);
|
|
3214
3923
|
}
|
|
3215
3924
|
const data = await parseJsonResponse(response, 'Dataset delete');
|
|
3925
|
+
if (hasJsonFlag()) {
|
|
3926
|
+
printJson({ dataset: data.dataset, deleted: true });
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3216
3929
|
printLine(`Deleted dataset ${sanitizeTerminalText(data.dataset.id)}.`);
|
|
3217
3930
|
}
|
|
3218
3931
|
async function lockDataset() {
|
|
@@ -3236,6 +3949,10 @@ async function lockDataset() {
|
|
|
3236
3949
|
throw new Error(`Lock failed: ${await response.text()}`);
|
|
3237
3950
|
}
|
|
3238
3951
|
const data = await parseJsonResponse(response, 'Dataset lock');
|
|
3952
|
+
if (hasJsonFlag()) {
|
|
3953
|
+
printJson({ dataset: data.dataset });
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3239
3956
|
printLine(`Locked dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) at ${sanitizeTerminalText(data.dataset.lockedAt)}. Row count: ${data.dataset.rowCount}`);
|
|
3240
3957
|
}
|
|
3241
3958
|
async function cloneDataset() {
|
|
@@ -3259,6 +3976,10 @@ async function cloneDataset() {
|
|
|
3259
3976
|
throw new Error(`Clone failed: ${await response.text()}`);
|
|
3260
3977
|
}
|
|
3261
3978
|
const data = await parseJsonResponse(response, 'Dataset clone');
|
|
3979
|
+
if (hasJsonFlag()) {
|
|
3980
|
+
printJson({ dataset: data.dataset });
|
|
3981
|
+
return;
|
|
3982
|
+
}
|
|
3262
3983
|
printLine(`Cloned dataset ${sanitizeTerminalText(data.dataset.parentDatasetId)} -> ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Row count: ${data.dataset.rowCount}`);
|
|
3263
3984
|
}
|
|
3264
3985
|
async function downloadAnnotations() {
|
|
@@ -3281,6 +4002,10 @@ async function downloadAnnotations() {
|
|
|
3281
4002
|
: fallbackName;
|
|
3282
4003
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
3283
4004
|
writeFileSync(filename, bytes);
|
|
4005
|
+
if (hasJsonFlag()) {
|
|
4006
|
+
printJson({ taskId, format, savedTo: filename });
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
3284
4009
|
printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`);
|
|
3285
4010
|
}
|
|
3286
4011
|
function readRunnerManifest(runnerDir) {
|
|
@@ -3493,7 +4218,12 @@ async function runnersExec() {
|
|
|
3493
4218
|
else {
|
|
3494
4219
|
writeFileSync(outPath, resultJsonl);
|
|
3495
4220
|
}
|
|
3496
|
-
|
|
4221
|
+
if (hasJsonFlag()) {
|
|
4222
|
+
printJson({ rowCount: context.rows.length, out: outPath });
|
|
4223
|
+
}
|
|
4224
|
+
else {
|
|
4225
|
+
printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`);
|
|
4226
|
+
}
|
|
3497
4227
|
}
|
|
3498
4228
|
finally {
|
|
3499
4229
|
materializedRunner.cleanup();
|
|
@@ -3527,6 +4257,9 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
3527
4257
|
}
|
|
3528
4258
|
const helpTarget = extractHelpTarget(cliArgs);
|
|
3529
4259
|
if (helpTarget !== null) {
|
|
4260
|
+
if (helpTarget.length === 0) {
|
|
4261
|
+
printBannerIfInteractive();
|
|
4262
|
+
}
|
|
3530
4263
|
printLine(renderHelpForArgs(helpTarget));
|
|
3531
4264
|
return;
|
|
3532
4265
|
}
|
|
@@ -3544,6 +4277,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
3544
4277
|
printCapabilities();
|
|
3545
4278
|
return;
|
|
3546
4279
|
}
|
|
4280
|
+
if (command === 'setup' && subcommand === 'prompt') {
|
|
4281
|
+
setupPromptCommand();
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
if (command === 'setup') {
|
|
4285
|
+
await setupCommand();
|
|
4286
|
+
return;
|
|
4287
|
+
}
|
|
3547
4288
|
if (command === 'install-skill') {
|
|
3548
4289
|
await installSkillCommand();
|
|
3549
4290
|
return;
|
|
@@ -3552,6 +4293,18 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
3552
4293
|
await installSkillCommand();
|
|
3553
4294
|
return;
|
|
3554
4295
|
}
|
|
4296
|
+
if (command === 'skills' && subcommand === 'path') {
|
|
4297
|
+
skillsPathCommand();
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
if (command === 'skills' && subcommand === 'status') {
|
|
4301
|
+
skillsStatusCommand();
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
if (command === 'skills' && subcommand === 'update') {
|
|
4305
|
+
await skillsUpdateCommand();
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
3555
4308
|
if (command === 'login') {
|
|
3556
4309
|
await login();
|
|
3557
4310
|
return;
|
|
@@ -3621,6 +4374,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
|
|
|
3621
4374
|
await pushPromptArtifact('prompt');
|
|
3622
4375
|
return;
|
|
3623
4376
|
}
|
|
4377
|
+
if (command === 'prompts' && subcommand === 'archive') {
|
|
4378
|
+
await setPromptArchivedState(true);
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
if (command === 'prompts' && subcommand === 'restore') {
|
|
4382
|
+
await setPromptArchivedState(false);
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
3624
4385
|
if (command === 'prompts' && subcommand === 'labels' && cliArgs[2] === 'set') {
|
|
3625
4386
|
await setPromptLabel();
|
|
3626
4387
|
return;
|