orizu 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,8 +18,11 @@ import { parseGlobalFlags } from './global-flags.js';
18
18
  import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js';
19
19
  import { runLocalAppPreview } from './preview-runtime.js';
20
20
  import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags, } from './http.js';
21
- import { getSkillInstallPath, installSkillTarget, isSkillInstallTarget, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
21
+ import { computeSkillContentHash, getSkillInstallPath, getSkillTargetStatus, getTargetForAgent, installSkillTarget, isSkillInstallAgent, isSkillInstallTarget, resolveSkillSource, SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, targetNeedsOverwrite, } from './skill-installer.js';
22
+ import { renderBanner } from './banner.js';
22
23
  import { formatTaskCreateError } from './task-create-error.js';
24
+ import { renderAgentSetupPrompt } from './setup-prompt.js';
25
+ import { getWorkspaceRoot, initOrizuWorkspace, WORKSPACE_DIR_NAME, workspaceExists, } from './workspace.js';
23
26
  const MAX_README_LENGTH = 200_000;
24
27
  function getCliVersion() {
25
28
  const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
@@ -34,14 +37,22 @@ function printLine(message = '') {
34
37
  function printVersion() {
35
38
  printLine(`orizu ${getCliVersion()}`);
36
39
  }
40
+ function printBannerIfInteractive() {
41
+ if (!process.stdout.isTTY) {
42
+ return;
43
+ }
44
+ printLine(renderBanner());
45
+ printLine('');
46
+ }
37
47
  function printUsage() {
48
+ printBannerIfInteractive();
38
49
  printLine(renderRootHelp());
39
50
  }
40
51
  function printPreviewUsage() {
41
52
  printLine(`\nLocal app preview:\n\n orizu apps preview --file <path> --input-schema <json-path> --output-schema <json-path> --sample-row <json-path> [--screenshot <png-path>] [--headed] [--keep-open] [--component <name>]`);
42
53
  }
43
54
  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]`);
55
+ printLine(`\nOptimization lifecycle commands:\n\n orizu optimizations start --project <team/project> --optimizer-version <id> --prompt-version <id[,id]> --selection-scorer <id> [--reflection-scorer <id>] [--pareto-scorer <id>] [--best-scorer <id>] --dataset-version <id> --split-set <id> [--train-split <name>] [--validation-split <name>] [--metadata <json|@file>] [--json]\n orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> --candidate-version-id <id> --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n>] [--num-threads auto|N] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--log-dir logs] [--no-skip-perfect-parent-reflection]\n orizu optimizations export <run-id> [--out <path>] [--json]\n orizu optimizations pause <run-id> [--reason <text>] [--json]\n orizu optimizations resume <run-id> [--json]\n orizu optimizations finish <run-id> [--best-score <n>] [--best-candidate <id>] [--result-prompt-version <id>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations fail <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--metadata <json|@file>] [--json]\n orizu optimizations cancel <run-id> [--reason <text>] [--report <markdown|@file> | --report-file <path>] [--json]`);
45
56
  }
46
57
  let cliArgs = process.argv.slice(2);
47
58
  let cliJsonOutput = false;
@@ -204,14 +215,16 @@ function printPromptSummaries(items, emptyMessage) {
204
215
  id: sanitizeTerminalText(item.id),
205
216
  name: sanitizeTerminalText(item.name),
206
217
  role: sanitizeTerminalText(item.role),
218
+ status: sanitizeTerminalText(item.status || 'active'),
207
219
  }));
208
220
  const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length));
209
221
  const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length));
210
222
  const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length));
211
- printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)}`);
212
- printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)}`);
223
+ const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length));
224
+ printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)} ${'STATUS'.padEnd(statusWidth)}`);
225
+ printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)} ${'-'.repeat(statusWidth)}`);
213
226
  rows.forEach(row => {
214
- printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)}`);
227
+ printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)} ${row.status.padEnd(statusWidth)}`);
215
228
  });
216
229
  }
217
230
  function printScorerSummaries(items) {
@@ -703,6 +716,10 @@ async function login() {
703
716
  const baseUrl = hasArg('--no-prompt-if-logged-in') ? getBaseUrl() : resolveLoginBaseUrl();
704
717
  assertSecureTokenTransport(baseUrl);
705
718
  if (hasArg('--no-prompt-if-logged-in') && getServerCredentials(baseUrl)) {
719
+ if (hasJsonFlag()) {
720
+ printJson({ status: 'already-logged-in', server: baseUrl });
721
+ return;
722
+ }
706
723
  printLine(`Already logged in to ${sanitizeTerminalText(baseUrl)}.`);
707
724
  return;
708
725
  }
@@ -781,6 +798,14 @@ async function login() {
781
798
  credentialType: 'pat',
782
799
  apiKey: loginData.apiKey,
783
800
  });
801
+ if (hasJsonFlag()) {
802
+ printJson({
803
+ status: 'logged-in',
804
+ server: baseUrl,
805
+ user: { id: loginData.user.id, email: loginData.user.email ?? null },
806
+ });
807
+ return;
808
+ }
784
809
  printLine(`Logged in as ${sanitizeTerminalText(loginData.user.email ?? loginData.user.id)}`);
785
810
  }
786
811
  async function whoami() {
@@ -789,12 +814,20 @@ async function whoami() {
789
814
  throw new Error(`whoami failed: ${await response.text()}`);
790
815
  }
791
816
  const data = await response.json();
817
+ if (hasJsonFlag()) {
818
+ printJson({ user: data.user, server: getBaseUrl() });
819
+ return;
820
+ }
792
821
  printLine(sanitizeTerminalText(data.user.email ?? data.user.id));
793
822
  }
794
823
  async function logout() {
795
824
  const baseUrl = getBaseUrl();
796
825
  const credentials = getServerCredentials(baseUrl);
797
826
  if (!credentials) {
827
+ if (hasJsonFlag()) {
828
+ printJson({ status: 'already-logged-out', server: baseUrl });
829
+ return;
830
+ }
798
831
  printLine(`Already logged out for ${sanitizeTerminalText(baseUrl)}.`);
799
832
  return;
800
833
  }
@@ -820,6 +853,10 @@ async function logout() {
820
853
  remoteLogoutError = sanitizeTerminalText(message).slice(0, 180);
821
854
  }
822
855
  clearServerCredentials(baseUrl);
856
+ if (hasJsonFlag()) {
857
+ printJson({ status: 'logged-out', server: baseUrl, remoteLogoutError });
858
+ return;
859
+ }
823
860
  if (remoteLogoutError) {
824
861
  console.warn(`Warning: remote logout failed: ${remoteLogoutError}`);
825
862
  }
@@ -832,6 +869,15 @@ async function printEnv() {
832
869
  const project = projectArg ? await resolveProjectSelection(projectArg) : null;
833
870
  const projectId = project?.id || getArg('--project-id') || process.env.ORIZU_PROJECT_ID || '';
834
871
  const projectSlug = project ? `${project.teamSlug}/${project.slug}` : process.env.ORIZU_PROJECT || '';
872
+ if (hasJsonFlag()) {
873
+ printJson({
874
+ ORIZU_API_URL: baseUrl,
875
+ ORIZU_TOKEN: token,
876
+ ORIZU_PROJECT_ID: projectId,
877
+ ...(projectSlug ? { ORIZU_PROJECT: projectSlug } : {}),
878
+ });
879
+ return;
880
+ }
835
881
  printLine(`export ORIZU_API_URL=${shellQuote(baseUrl)}`);
836
882
  printLine(`export ORIZU_TOKEN=${shellQuote(token)}`);
837
883
  printLine(`export ORIZU_PROJECT_ID=${shellQuote(projectId)}`);
@@ -868,24 +914,46 @@ async function logOptimizationEvent() {
868
914
  throw new Error(`Failed to log event: ${await response.text()}`);
869
915
  }
870
916
  const data = await parseJsonResponse(response, 'Optimization event log');
917
+ if (hasJsonFlag()) {
918
+ printJson({ eventId: data.eventId });
919
+ return;
920
+ }
871
921
  printLine(`Logged event ${sanitizeTerminalText(data.eventId)}`);
872
922
  }
873
923
  async function listPrompts() {
874
924
  const project = getArg('--project') || await resolveProjectSlug(null);
875
- const response = await authedFetch(`/api/cli/prompts?project=${encodeURIComponent(project)}`);
925
+ const status = getArg('--status') || 'active';
926
+ if (!['active', 'archived', 'all'].includes(status)) {
927
+ throw new Error('Usage: orizu prompts list --project <team/project> [--status active|archived|all]');
928
+ }
929
+ const params = new URLSearchParams({ project, status });
930
+ const response = await authedFetch(`/api/cli/prompts?${params.toString()}`);
876
931
  if (!response.ok) {
877
932
  throw new Error(`Failed to fetch prompts: ${await response.text()}`);
878
933
  }
879
934
  const data = await parseJsonResponse(response, 'Prompts list');
935
+ if (hasJsonFlag()) {
936
+ printJson(data);
937
+ return;
938
+ }
880
939
  printPromptSummaries(data.prompts, 'No prompts found.');
881
940
  }
882
941
  async function listJudges() {
883
942
  const project = getArg('--project') || await resolveProjectSlug(null);
884
- const response = await authedFetch(`/api/cli/judges?project=${encodeURIComponent(project)}`);
943
+ const status = getArg('--status') || 'active';
944
+ if (!['active', 'archived', 'all'].includes(status)) {
945
+ throw new Error('Usage: orizu judges list --project <team/project> [--status active|archived|all]');
946
+ }
947
+ const params = new URLSearchParams({ project, status });
948
+ const response = await authedFetch(`/api/cli/judges?${params.toString()}`);
885
949
  if (!response.ok) {
886
950
  throw new Error(`Failed to fetch judges: ${await response.text()}`);
887
951
  }
888
952
  const data = await parseJsonResponse(response, 'Judges list');
953
+ if (hasJsonFlag()) {
954
+ printJson(data);
955
+ return;
956
+ }
889
957
  printPromptSummaries(data.judges, 'No judges found.');
890
958
  }
891
959
  async function listPromptComments() {
@@ -922,6 +990,10 @@ async function listScorers() {
922
990
  throw new Error(`Failed to fetch scorers: ${await response.text()}`);
923
991
  }
924
992
  const data = await parseJsonResponse(response, 'Scorers list');
993
+ if (hasJsonFlag()) {
994
+ printJson(data);
995
+ return;
996
+ }
925
997
  printScorerSummaries(data.scorers);
926
998
  }
927
999
  async function registerScorer() {
@@ -1203,6 +1275,28 @@ async function setPromptLabel() {
1203
1275
  }
1204
1276
  printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`);
1205
1277
  }
1278
+ async function setPromptArchivedState(archived) {
1279
+ const promptRef = getPositionalArg(2);
1280
+ const project = getArg('--project') || await resolveProjectSlug(null);
1281
+ const command = archived ? 'archive' : 'restore';
1282
+ if (!promptRef) {
1283
+ throw new Error(`Usage: orizu prompts ${command} <prompt-id-or-name> --project <team/project> [--json]`);
1284
+ }
1285
+ const response = await authedFetch(`/api/cli/prompts/${encodeURIComponent(promptRef)}?project=${encodeURIComponent(project)}`, {
1286
+ method: 'PATCH',
1287
+ headers: { 'Content-Type': 'application/json' },
1288
+ body: JSON.stringify({ archived }),
1289
+ });
1290
+ if (!response.ok) {
1291
+ throw new Error(`Failed to ${command} prompt: ${await response.text()}`);
1292
+ }
1293
+ const data = await parseJsonResponse(response, `Prompt ${command}`);
1294
+ if (hasJsonFlag()) {
1295
+ printJson(data);
1296
+ return;
1297
+ }
1298
+ printLine(`${archived ? 'Archived' : 'Restored'} prompt ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)})`);
1299
+ }
1206
1300
  async function bindPromptScorer(role) {
1207
1301
  const promptId = getPositionalArg(3);
1208
1302
  const project = getArg('--project') || await resolveProjectSlug(null);
@@ -1400,6 +1494,10 @@ async function submitRunResults() {
1400
1494
  throw new Error(`Failed to submit run: ${await response.text()}`);
1401
1495
  }
1402
1496
  const data = await parseJsonResponse(response, 'Run submit');
1497
+ if (hasJsonFlag()) {
1498
+ printJson({ run: data.run });
1499
+ return;
1500
+ }
1403
1501
  printLine(`Submitted run ${sanitizeTerminalText(data.run.id)} ` +
1404
1502
  `(${formatPercent(data.run.aggregateScore)}) -> ` +
1405
1503
  sanitizeTerminalText(data.run.perRowResultsStoragePath));
@@ -1857,9 +1955,17 @@ async function runGepaOptimization() {
1857
1955
  if (result.status !== 0) {
1858
1956
  throw new Error(`orizu-gepa failed with exit code ${result.status}`);
1859
1957
  }
1958
+ if (hasJsonFlag()) {
1959
+ printJson({ status: 'completed', exitCode: 0 });
1960
+ }
1860
1961
  }
1861
1962
  async function listTeams() {
1862
- printTeams(await fetchTeams());
1963
+ const teams = await fetchTeams();
1964
+ if (hasJsonFlag()) {
1965
+ printJson({ teams });
1966
+ return;
1967
+ }
1968
+ printTeams(teams);
1863
1969
  }
1864
1970
  async function resolveTeamSlug(teamSlugArg) {
1865
1971
  if (teamSlugArg) {
@@ -1891,12 +1997,21 @@ async function createTeam() {
1891
1997
  throw new Error(`Failed to create team: ${await response.text()}`);
1892
1998
  }
1893
1999
  const data = await parseJsonResponse(response, 'Team create');
2000
+ if (hasJsonFlag()) {
2001
+ printJson({ team: data.team });
2002
+ return;
2003
+ }
1894
2004
  printLine(`Created team: ${sanitizeTerminalText(data.team.name)} (${sanitizeTerminalText(data.team.slug)})`);
1895
2005
  }
1896
2006
  async function listProjects() {
1897
2007
  const teamSlugArg = getArg('--team');
1898
2008
  const teamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null;
1899
- printProjects(await fetchProjects(teamSlug || undefined));
2009
+ const projects = await fetchProjects(teamSlug || undefined);
2010
+ if (hasJsonFlag()) {
2011
+ printJson({ projects });
2012
+ return;
2013
+ }
2014
+ printProjects(projects);
1900
2015
  }
1901
2016
  async function createProject() {
1902
2017
  const name = getArg('--name');
@@ -1918,15 +2033,29 @@ async function createProject() {
1918
2033
  throw new Error(`Failed to create project: ${await response.text()}`);
1919
2034
  }
1920
2035
  const data = await parseJsonResponse(response, 'Project create');
2036
+ if (hasJsonFlag()) {
2037
+ printJson({ project: data.project });
2038
+ return;
2039
+ }
1921
2040
  printLine(`Created project ${sanitizeTerminalText(`${data.project.teamSlug}/${data.project.slug}`)}`);
1922
2041
  }
1923
2042
  async function listTasks() {
1924
2043
  const project = getArg('--project');
1925
- printTasks(await fetchTasks(project || undefined));
2044
+ const tasks = await fetchTasks(project || undefined);
2045
+ if (hasJsonFlag()) {
2046
+ printJson({ tasks });
2047
+ return;
2048
+ }
2049
+ printTasks(tasks);
1926
2050
  }
1927
2051
  async function listApps() {
1928
2052
  const project = getArg('--project') || await resolveProjectSlug(null);
1929
- printApps(await fetchApps(project));
2053
+ const apps = await fetchApps(project);
2054
+ if (hasJsonFlag()) {
2055
+ printJson({ apps });
2056
+ return;
2057
+ }
2058
+ printApps(apps);
1930
2059
  }
1931
2060
  function readSourceFile(pathArg) {
1932
2061
  const expandedPath = expandHomePath(pathArg);
@@ -1986,24 +2115,82 @@ async function askYesNo(question, defaultYes) {
1986
2115
  rl.close();
1987
2116
  }
1988
2117
  }
1989
- async function promptSkillInstallTargets() {
2118
+ const SKILL_INSTALL_USAGE = `Usage: orizu install-skill [--agent <${SKILL_INSTALL_AGENTS.join('|')}>]... [--scope global|project] [--mode auto|link|copy] [--target <${SKILL_INSTALL_TARGETS.join('|')}>]... [--yes] [--dry-run]`;
2119
+ function describeSkillTarget(target) {
2120
+ if (target === 'claude-user')
2121
+ return 'Claude Code, for you across all projects';
2122
+ if (target === 'claude-project')
2123
+ return 'Claude Code, only for this project';
2124
+ if (target === 'agent-user')
2125
+ return 'Codex / Open Agent Skills, for you across all projects';
2126
+ if (target === 'agents-project')
2127
+ return 'Codex / Open Agent Skills, only for this project';
2128
+ if (target === 'codex-project')
2129
+ return 'Codex legacy project folder (.codex/skills)';
2130
+ return 'Project instructions, managed Orizu section in AGENTS.md';
2131
+ }
2132
+ async function promptSkillInstallTargets(scope) {
1990
2133
  if (!isInteractiveTerminal()) {
1991
- throw new Error(`Usage: orizu install-skill --target <${SKILL_INSTALL_TARGETS.join('|')}> [--yes] [--dry-run]`);
2134
+ throw new Error(SKILL_INSTALL_USAGE);
1992
2135
  }
1993
- printLine('Where should the Orizu CLI skill be installed?');
2136
+ const choices = [
2137
+ { label: describeSkillTarget(getTargetForAgent('claude', scope)), target: getTargetForAgent('claude', scope), defaultYes: true },
2138
+ { label: describeSkillTarget(getTargetForAgent('codex', scope)), target: getTargetForAgent('codex', scope), defaultYes: true },
2139
+ { label: describeSkillTarget('agents-md'), target: 'agents-md', defaultYes: false },
2140
+ ];
2141
+ printLine('Where should Orizu be available?');
1994
2142
  const targets = [];
1995
- for (const target of SKILL_INSTALL_TARGETS) {
1996
- const defaultYes = target === 'agent-user';
1997
- if (await askYesNo(` Install to ${target}?`, defaultYes)) {
1998
- targets.push(target);
2143
+ for (const choice of choices) {
2144
+ const installPath = getSkillInstallPath(choice.target);
2145
+ if (await askYesNo(` ${choice.label}\n ${installPath}?`, choice.defaultYes)) {
2146
+ targets.push(choice.target);
2147
+ }
2148
+ }
2149
+ if (await askYesNo(' Show advanced targets?', false)) {
2150
+ for (const target of SKILL_INSTALL_TARGETS) {
2151
+ if (targets.includes(target)) {
2152
+ continue;
2153
+ }
2154
+ const installPath = getSkillInstallPath(target);
2155
+ if (await askYesNo(` ${describeSkillTarget(target)} [${target}]\n ${installPath}?`, false)) {
2156
+ targets.push(target);
2157
+ }
1999
2158
  }
2000
2159
  }
2001
2160
  return targets;
2002
2161
  }
2003
- function parseSkillInstallTargets() {
2004
- const rawTargets = getArgs('--target');
2162
+ function parseSkillInstallScope() {
2163
+ const raw = getArg('--scope');
2164
+ if (raw === null || raw === 'global') {
2165
+ return 'global';
2166
+ }
2167
+ if (raw === 'project') {
2168
+ return 'local';
2169
+ }
2170
+ throw new Error(`Unknown --scope '${raw}'. Choices: global, project.`);
2171
+ }
2172
+ function parseSkillInstallMode() {
2173
+ const raw = getArg('--mode');
2174
+ if (raw === null) {
2175
+ return 'auto';
2176
+ }
2177
+ if (raw !== 'auto' && raw !== 'link' && raw !== 'copy') {
2178
+ throw new Error(`Unknown --mode '${raw}'. Choices: auto, link, copy.`);
2179
+ }
2180
+ return raw;
2181
+ }
2182
+ function parseSkillInstallTargets(scope) {
2005
2183
  const targets = [];
2006
- for (const target of rawTargets) {
2184
+ for (const agent of getArgs('--agent')) {
2185
+ if (!isSkillInstallAgent(agent)) {
2186
+ throw new Error(`Unknown agent '${agent}'. Available agents: ${SKILL_INSTALL_AGENTS.join(', ')}`);
2187
+ }
2188
+ const target = getTargetForAgent(agent, scope);
2189
+ if (!targets.includes(target)) {
2190
+ targets.push(target);
2191
+ }
2192
+ }
2193
+ for (const target of getArgs('--target')) {
2007
2194
  if (!isSkillInstallTarget(target)) {
2008
2195
  throw new Error(`Unknown skill install target '${target}'. Available targets: ${SKILL_INSTALL_TARGETS.join(', ')}`);
2009
2196
  }
@@ -2022,36 +2209,412 @@ function formatSkillInstallAction(action) {
2022
2209
  return 'Would write';
2023
2210
  return 'Would update';
2024
2211
  }
2025
- async function installSkillCommand() {
2026
- const skipConfirm = hasArg('--yes');
2027
- const dryRun = hasArg('--dry-run');
2028
- let targets = parseSkillInstallTargets();
2029
- if (targets.length === 0) {
2030
- targets = await promptSkillInstallTargets();
2212
+ function describePlannedWrite(target, action, mode, path) {
2213
+ const verb = action.includes('create') || action === 'created' ? 'create' : 'replace';
2214
+ if (target === 'agents-md') {
2215
+ return ` ${path} (${verb} managed Orizu section)`;
2031
2216
  }
2032
- if (targets.length === 0) {
2033
- printLine('No targets selected; nothing to do.');
2034
- return;
2217
+ if (mode === 'link') {
2218
+ return ` ${path} (${verb}, symlink to the CLI-managed skill)`;
2219
+ }
2220
+ return ` ${path} (${verb}, full copy with sync metadata)`;
2221
+ }
2222
+ async function applySkillInstallTargets(targets, options) {
2223
+ const cliVersion = getCliVersion();
2224
+ const jsonMode = hasJsonFlag();
2225
+ const planned = targets.map(target => installSkillTarget(target, { dryRun: true, mode: options.mode, cliVersion }));
2226
+ if (!jsonMode) {
2227
+ printLine('Planned writes:');
2228
+ for (const plan of planned) {
2229
+ printLine(describePlannedWrite(plan.target, plan.action, plan.mode, plan.path));
2230
+ }
2231
+ printLine('');
2035
2232
  }
2233
+ if (options.dryRun) {
2234
+ if (!jsonMode) {
2235
+ printLine('Dry run: no files were changed.');
2236
+ }
2237
+ return planned.map(plan => ({
2238
+ target: plan.target,
2239
+ path: plan.path,
2240
+ action: 'skipped',
2241
+ mode: plan.mode,
2242
+ }));
2243
+ }
2244
+ const outcomes = [];
2036
2245
  for (const target of targets) {
2037
- let overwrite = skipConfirm;
2246
+ let overwrite = options.skipConfirm;
2038
2247
  const installPath = getSkillInstallPath(target);
2039
- if (!dryRun && !overwrite && targetNeedsOverwrite(target)) {
2248
+ if (!overwrite && targetNeedsOverwrite(target)) {
2040
2249
  if (!isInteractiveTerminal()) {
2041
2250
  throw new Error(`${installPath} already exists. Pass --yes to replace it.`);
2042
2251
  }
2043
2252
  overwrite = await askYesNo(`Replace ${installPath}?`, true);
2044
2253
  if (!overwrite) {
2045
- printLine(`Skipped ${installPath}`);
2254
+ if (!jsonMode) {
2255
+ printLine(`Skipped ${installPath}`);
2256
+ }
2257
+ outcomes.push({ target, path: installPath, action: 'skipped', mode: null });
2046
2258
  continue;
2047
2259
  }
2048
2260
  }
2261
+ try {
2262
+ const result = installSkillTarget(target, {
2263
+ overwrite,
2264
+ mode: options.mode,
2265
+ cliVersion,
2266
+ });
2267
+ if (!jsonMode) {
2268
+ const suffix = result.mode === 'link' ? ' (symlink)' : '';
2269
+ printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`);
2270
+ }
2271
+ outcomes.push({
2272
+ target,
2273
+ path: result.path,
2274
+ action: result.action,
2275
+ mode: result.mode,
2276
+ });
2277
+ }
2278
+ catch (error) {
2279
+ const message = error?.message || String(error);
2280
+ if (!jsonMode) {
2281
+ printLine(`Failed ${installPath}: ${message}`);
2282
+ }
2283
+ outcomes.push({ target, path: installPath, action: 'failed', mode: null, error: message });
2284
+ }
2285
+ }
2286
+ return outcomes;
2287
+ }
2288
+ async function installSkillCommand() {
2289
+ const skipConfirm = hasArg('--yes');
2290
+ const dryRun = hasArg('--dry-run');
2291
+ const scope = parseSkillInstallScope();
2292
+ const mode = parseSkillInstallMode();
2293
+ let targets = parseSkillInstallTargets(scope);
2294
+ if (targets.length === 0) {
2295
+ targets = await promptSkillInstallTargets(scope);
2296
+ }
2297
+ if (targets.length === 0) {
2298
+ if (hasJsonFlag()) {
2299
+ printJson({ installs: [] });
2300
+ return;
2301
+ }
2302
+ printLine('No targets selected; nothing to do.');
2303
+ return;
2304
+ }
2305
+ const outcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
2306
+ if (hasJsonFlag()) {
2307
+ printJson({ dryRun, installs: outcomes });
2308
+ }
2309
+ }
2310
+ function describeSkillTargetState(status) {
2311
+ if (status.state === 'current')
2312
+ return 'current';
2313
+ if (status.state === 'stale')
2314
+ return 'stale (run `orizu skills update`)';
2315
+ if (status.state === 'broken-link')
2316
+ return 'broken symlink (run `orizu skills update`)';
2317
+ if (status.state === 'unmanaged')
2318
+ return 'exists without a managed Orizu section';
2319
+ return 'not installed';
2320
+ }
2321
+ function skillsStatusCommand() {
2322
+ const source = resolveSkillSource();
2323
+ const statuses = SKILL_INSTALL_TARGETS.map(target => getSkillTargetStatus(target));
2324
+ if (hasJsonFlag()) {
2325
+ printJson({
2326
+ name: source.name,
2327
+ root: source.root,
2328
+ source: source.source,
2329
+ cliVersion: getCliVersion(),
2330
+ sourceHash: computeSkillContentHash(source.root),
2331
+ targets: statuses.map(status => ({
2332
+ target: status.target,
2333
+ path: status.path,
2334
+ state: status.state,
2335
+ mode: status.mode,
2336
+ linkTarget: status.linkTarget,
2337
+ installedHash: status.installedHash,
2338
+ meta: status.meta,
2339
+ })),
2340
+ });
2341
+ return;
2342
+ }
2343
+ printLine(`Skill source: ${source.root} (${source.source})`);
2344
+ printLine('');
2345
+ for (const status of statuses) {
2346
+ const mode = status.mode ? `, ${status.mode}` : '';
2347
+ printLine(` ${status.target.padEnd(16)} ${describeSkillTargetState(status)}${mode}`);
2348
+ printLine(` ${''.padEnd(16)} ${status.path}`);
2349
+ }
2350
+ }
2351
+ async function skillsUpdateCommand() {
2352
+ const dryRun = hasArg('--dry-run');
2353
+ const cliVersion = getCliVersion();
2354
+ const updates = [];
2355
+ for (const target of SKILL_INSTALL_TARGETS) {
2356
+ const status = getSkillTargetStatus(target);
2357
+ if (status.state === 'missing' || status.state === 'unmanaged') {
2358
+ continue;
2359
+ }
2360
+ if (status.state === 'current') {
2361
+ updates.push({ target, path: status.path, action: 'already-current' });
2362
+ continue;
2363
+ }
2364
+ if (dryRun) {
2365
+ updates.push({ target, path: status.path, action: 'would-update' });
2366
+ continue;
2367
+ }
2368
+ const refreshMode = status.mode === 'link' ? 'auto' : 'copy';
2049
2369
  const result = installSkillTarget(target, {
2050
- overwrite,
2051
- dryRun,
2370
+ overwrite: true,
2371
+ mode: refreshMode,
2372
+ cliVersion,
2373
+ });
2374
+ updates.push({
2375
+ target,
2376
+ path: status.path,
2377
+ action: status.state === 'broken-link' ? 'relinked' : result.action,
2378
+ });
2379
+ }
2380
+ if (hasJsonFlag()) {
2381
+ printJson({ updates });
2382
+ return;
2383
+ }
2384
+ if (updates.length === 0) {
2385
+ printLine('No Orizu skill installs found. Run `orizu install-skill` first.');
2386
+ return;
2387
+ }
2388
+ for (const update of updates) {
2389
+ if (update.action === 'already-current') {
2390
+ printLine(`Current ${update.path}`);
2391
+ }
2392
+ else if (update.action === 'would-update') {
2393
+ printLine(`Would update ${update.path}`);
2394
+ }
2395
+ else if (update.action === 'relinked') {
2396
+ printLine(`Relinked ${update.path}`);
2397
+ }
2398
+ else {
2399
+ printLine(`Updated ${update.path}`);
2400
+ }
2401
+ }
2402
+ }
2403
+ function skillsPathCommand() {
2404
+ const source = resolveSkillSource();
2405
+ if (hasJsonFlag()) {
2406
+ printJson({
2407
+ name: source.name,
2408
+ root: source.root,
2409
+ skillMd: source.skillMd,
2410
+ source: source.source,
2411
+ cliVersion: getCliVersion(),
2412
+ skillHash: computeSkillContentHash(source.root),
2052
2413
  });
2053
- printLine(`${formatSkillInstallAction(result.action)} ${result.path}`);
2414
+ return;
2054
2415
  }
2416
+ printLine(hasArg('--skill-md') ? source.skillMd : source.root);
2417
+ }
2418
+ const LAUNCHABLE_AGENTS = {
2419
+ claude: 'claude',
2420
+ codex: 'codex',
2421
+ };
2422
+ function findExecutable(name) {
2423
+ const pathValue = process.env.PATH || '';
2424
+ for (const dir of pathValue.split(delimiter)) {
2425
+ if (!dir) {
2426
+ continue;
2427
+ }
2428
+ const candidate = join(dir, name);
2429
+ if (existsSync(candidate)) {
2430
+ return candidate;
2431
+ }
2432
+ }
2433
+ return null;
2434
+ }
2435
+ function describeSetupAuthState() {
2436
+ const baseUrl = getBaseUrl();
2437
+ return {
2438
+ state: getServerCredentials(baseUrl) ? 'signed-in' : 'signed-out',
2439
+ baseUrl,
2440
+ };
2441
+ }
2442
+ async function setupCommand() {
2443
+ const dryRun = hasArg('--dry-run');
2444
+ const skipConfirm = hasArg('--yes');
2445
+ const noInput = hasArg('--no-input') || !isInteractiveTerminal();
2446
+ printBannerIfInteractive();
2447
+ printLine('Orizu setup');
2448
+ printLine('');
2449
+ // Phase 1: login
2450
+ printLine('1. Authentication');
2451
+ let auth = describeSetupAuthState();
2452
+ if (hasArg('--skip-login')) {
2453
+ printLine(' Skipped (--skip-login).');
2454
+ }
2455
+ else if (auth.state === 'signed-in') {
2456
+ printLine(` Already authenticated with ${sanitizeTerminalText(auth.baseUrl)}.`);
2457
+ }
2458
+ else if (noInput || dryRun) {
2459
+ printLine(' Not signed in. Run `orizu login` to authenticate.');
2460
+ }
2461
+ else if (await askYesNo(' Sign in to Orizu now?', true)) {
2462
+ await login();
2463
+ auth = describeSetupAuthState();
2464
+ }
2465
+ else {
2466
+ printLine(' Skipped. Run `orizu login` when ready.');
2467
+ }
2468
+ printLine('');
2469
+ // Phase 2: agent integration
2470
+ printLine('2. Agent integration');
2471
+ const scope = parseSkillInstallScope();
2472
+ const mode = parseSkillInstallMode();
2473
+ let targets = hasArg('--no-install') ? [] : parseSkillInstallTargets(scope);
2474
+ if (targets.length === 0 && !hasArg('--no-install')) {
2475
+ if (noInput) {
2476
+ printLine(' No agents selected. Pass --agent claude and/or --agent codex, or rerun interactively.');
2477
+ }
2478
+ else {
2479
+ targets = await promptSkillInstallTargets(scope);
2480
+ }
2481
+ }
2482
+ let installOutcomes = [];
2483
+ if (targets.length > 0) {
2484
+ installOutcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
2485
+ }
2486
+ else if (hasArg('--no-install')) {
2487
+ printLine(' Skipped (--no-install).');
2488
+ }
2489
+ printLine('');
2490
+ // Phase 3: local workspace
2491
+ printLine('3. Local workspace');
2492
+ let workspaceState = 'skipped';
2493
+ if (hasArg('--no-workspace')) {
2494
+ printLine(' Skipped (--no-workspace).');
2495
+ }
2496
+ else if (workspaceExists()) {
2497
+ workspaceState = 'exists';
2498
+ printLine(` ${WORKSPACE_DIR_NAME}/ already initialized.`);
2499
+ }
2500
+ else {
2501
+ const wantsWorkspace = hasArg('--workspace') ||
2502
+ (!noInput && await askYesNo(` Create a gitignored ${WORKSPACE_DIR_NAME}/ workspace for local Orizu artifacts?`, true));
2503
+ if (wantsWorkspace) {
2504
+ const result = initOrizuWorkspace({
2505
+ baseUrl: auth.baseUrl,
2506
+ cliVersion: getCliVersion(),
2507
+ dryRun,
2508
+ });
2509
+ workspaceState = result.state === 'would-create' ? 'would-create' : result.state;
2510
+ for (const action of result.actions) {
2511
+ printLine(` ${dryRun ? 'Would ' : 'Did '}${action}`);
2512
+ }
2513
+ }
2514
+ else {
2515
+ printLine(` Skipped. Rerun with --workspace to create ${WORKSPACE_DIR_NAME}/.`);
2516
+ }
2517
+ }
2518
+ printLine('');
2519
+ // Phase 4: coding-agent handoff
2520
+ const handoffPrompt = renderAgentSetupPrompt({
2521
+ workspacePath: workspaceState === 'created' || workspaceState === 'exists'
2522
+ ? getWorkspaceRoot()
2523
+ : null,
2524
+ });
2525
+ if (!hasArg('--no-handoff')) {
2526
+ printLine('4. Coding-agent handoff');
2527
+ printLine(' Give your coding agent this prompt to plan repo-specific Orizu adoption:');
2528
+ printLine('');
2529
+ printLine('--- prompt start ---');
2530
+ printLine(handoffPrompt);
2531
+ printLine('--- prompt end ---');
2532
+ printLine('');
2533
+ const detected = Object.keys(LAUNCHABLE_AGENTS).filter(agent => findExecutable(LAUNCHABLE_AGENTS[agent]));
2534
+ if (detected.length > 0) {
2535
+ printLine(` Detected agents: ${detected.join(', ')}. Launch one with the prompt, e.g.:`);
2536
+ for (const agent of detected) {
2537
+ printLine(` ${LAUNCHABLE_AGENTS[agent]} "$(orizu setup prompt)"`);
2538
+ }
2539
+ }
2540
+ const launchAgent = getArg('--launch');
2541
+ if (launchAgent) {
2542
+ if (!(launchAgent in LAUNCHABLE_AGENTS)) {
2543
+ throw new Error(`Unknown --launch agent '${launchAgent}'. Choices: ${Object.keys(LAUNCHABLE_AGENTS).join(', ')}.`);
2544
+ }
2545
+ const binary = findExecutable(LAUNCHABLE_AGENTS[launchAgent]);
2546
+ if (!isInteractiveTerminal()) {
2547
+ printLine(' Not launching: --launch requires an interactive terminal.');
2548
+ }
2549
+ else if (!binary) {
2550
+ printLine(` Not launching: '${LAUNCHABLE_AGENTS[launchAgent]}' was not found on PATH.`);
2551
+ }
2552
+ else if (dryRun) {
2553
+ printLine(` Would launch ${binary} with the setup prompt.`);
2554
+ }
2555
+ else if (skipConfirm || await askYesNo(` Launch ${LAUNCHABLE_AGENTS[launchAgent]} with the setup prompt now?`, true)) {
2556
+ printLine(` Launching ${binary}…`);
2557
+ spawnSync(binary, [handoffPrompt], { stdio: 'inherit' });
2558
+ }
2559
+ }
2560
+ printLine('');
2561
+ }
2562
+ // Setup summary (success criteria checklist)
2563
+ const summaryTargets = installOutcomes.length > 0
2564
+ ? installOutcomes
2565
+ .filter(outcome => outcome.action !== 'failed')
2566
+ .map(outcome => ({ outcome, status: getSkillTargetStatus(outcome.target) }))
2567
+ : [];
2568
+ const source = resolveSkillSource();
2569
+ if (hasJsonFlag()) {
2570
+ printJson({
2571
+ auth: { state: auth.state, server: auth.baseUrl },
2572
+ integrations: installOutcomes.map(outcome => ({
2573
+ target: outcome.target,
2574
+ path: outcome.path,
2575
+ action: outcome.action,
2576
+ mode: outcome.mode,
2577
+ error: outcome.error,
2578
+ })),
2579
+ skill: {
2580
+ root: source.root,
2581
+ source: source.source,
2582
+ cliVersion: getCliVersion(),
2583
+ skillHash: computeSkillContentHash(source.root),
2584
+ },
2585
+ workspace: { state: workspaceState, path: getWorkspaceRoot() },
2586
+ });
2587
+ return;
2588
+ }
2589
+ printLine('Setup summary');
2590
+ printLine(` Auth: ${auth.state === 'signed-in' ? `signed in (${sanitizeTerminalText(auth.baseUrl)})` : 'not signed in — run `orizu login`'}`);
2591
+ if (summaryTargets.length > 0) {
2592
+ printLine(' Integrations:');
2593
+ for (const { outcome, status } of summaryTargets) {
2594
+ const modeLabel = status.mode ? `, ${status.mode}` : '';
2595
+ printLine(` ${outcome.target.padEnd(16)} ${status.state}${modeLabel} ${outcome.path}`);
2596
+ }
2597
+ }
2598
+ else {
2599
+ printLine(' Integrations: none installed this run — `orizu install-skill` sets them up.');
2600
+ }
2601
+ const failed = installOutcomes.filter(outcome => outcome.action === 'failed');
2602
+ for (const failure of failed) {
2603
+ printLine(` ${failure.target.padEnd(16)} failed: ${failure.error}`);
2604
+ }
2605
+ printLine(` Skill source: ${source.root} (${source.source})`);
2606
+ printLine(` Workspace: ${workspaceState === 'skipped' ? 'skipped' : `${getWorkspaceRoot()} (${workspaceState})`}`);
2607
+ printLine(' Next: hand the prompt above to your coding agent to plan repo-specific Orizu adoption.');
2608
+ }
2609
+ function setupPromptCommand() {
2610
+ const prompt = renderAgentSetupPrompt({
2611
+ workspacePath: workspaceExists() ? getWorkspaceRoot() : null,
2612
+ });
2613
+ if (hasJsonFlag()) {
2614
+ printJson({ prompt });
2615
+ return;
2616
+ }
2617
+ printLine(prompt);
2055
2618
  }
2056
2619
  function readJsonFile(pathArg) {
2057
2620
  const raw = readSourceFile(pathArg);
@@ -2281,6 +2844,10 @@ async function createAppFromFile() {
2281
2844
  throw new Error(`Failed to create app: ${await response.text()}`);
2282
2845
  }
2283
2846
  const data = await parseJsonResponse(response, 'App create');
2847
+ if (hasJsonFlag()) {
2848
+ printJson({ app: data.app, warnings: data.warnings || [] });
2849
+ return;
2850
+ }
2284
2851
  printLine(`Created app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) v${data.app.versionNum}`);
2285
2852
  if (data.app.url) {
2286
2853
  printLine(`View app: ${formatTerminalLink(data.app.url)}`);
@@ -2316,6 +2883,14 @@ async function previewAppFromFile() {
2316
2883
  headed,
2317
2884
  keepOpen,
2318
2885
  });
2886
+ if (hasJsonFlag()) {
2887
+ printJson({
2888
+ url: result.url,
2889
+ screenshotPath: result.screenshotPath || null,
2890
+ warnings: result.warnings,
2891
+ });
2892
+ return;
2893
+ }
2319
2894
  printLine(`Preview rendered: ${formatTerminalLink(result.url)}`);
2320
2895
  if (result.screenshotPath) {
2321
2896
  printLine(`Screenshot: ${sanitizeTerminalText(result.screenshotPath)}`);
@@ -2358,6 +2933,10 @@ async function updateAppFromFile() {
2358
2933
  throw new Error(`Failed to update app: ${await response.text()}`);
2359
2934
  }
2360
2935
  const data = await parseJsonResponse(response, 'App update');
2936
+ if (hasJsonFlag()) {
2937
+ printJson({ app: data.app, warnings: data.warnings || [] });
2938
+ return;
2939
+ }
2361
2940
  printLine(`Updated app ${sanitizeTerminalText(data.app.name)} (${sanitizeTerminalText(data.app.id)}) to v${data.app.versionNum}`);
2362
2941
  if (data.warnings?.length) {
2363
2942
  printLine(`Warnings: ${sanitizeTerminalText(data.warnings.join('; '))}`);
@@ -2392,6 +2971,10 @@ async function linkAppDataset() {
2392
2971
  throw new Error(`Failed to link dataset: ${await response.text()}`);
2393
2972
  }
2394
2973
  const data = await parseJsonResponse(response, 'App link dataset');
2974
+ if (hasJsonFlag()) {
2975
+ printJson({ app: data.app, linkedDataset: data.linkedDataset, versionNum: data.versionNum });
2976
+ return;
2977
+ }
2395
2978
  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
2979
  }
2397
2980
  function parseCommaSeparated(value) {
@@ -2439,7 +3022,7 @@ async function createTask() {
2439
3022
  });
2440
3023
  if (!response.ok) {
2441
3024
  const cliError = await formatTaskCreateError(response);
2442
- if (hasArg('--json')) {
3025
+ if (hasJsonFlag()) {
2443
3026
  const payload = cliError.structuredPayload ?? { error: cliError.message };
2444
3027
  printLine(JSON.stringify({
2445
3028
  ...payload,
@@ -2449,7 +3032,7 @@ async function createTask() {
2449
3032
  throw cliError;
2450
3033
  }
2451
3034
  const data = await parseJsonResponse(response, 'Task create');
2452
- if (hasArg('--json')) {
3035
+ if (hasJsonFlag()) {
2453
3036
  printLine(JSON.stringify({
2454
3037
  taskId: data.task.id,
2455
3038
  datasetId,
@@ -2502,7 +3085,7 @@ async function publishTask() {
2502
3085
  throw new Error(`Failed to publish task: ${await extractErrorMessage(response)}`);
2503
3086
  }
2504
3087
  const data = await parseJsonResponse(response, 'Task publish');
2505
- if (hasArg('--json')) {
3088
+ if (hasJsonFlag()) {
2506
3089
  printLine(JSON.stringify({
2507
3090
  taskId: data.task.id,
2508
3091
  status: data.task.status,
@@ -2540,6 +3123,10 @@ async function assignTask() {
2540
3123
  throw new Error(`Failed to assign task: ${await response.text()}`);
2541
3124
  }
2542
3125
  const data = await parseJsonResponse(response, 'Task assign');
3126
+ if (hasJsonFlag()) {
3127
+ printJson({ assignmentsCreated: data.assignmentsCreated });
3128
+ return;
3129
+ }
2543
3130
  printLine(`Created ${data.assignmentsCreated} assignments.`);
2544
3131
  }
2545
3132
  async function taskStatus() {
@@ -2550,7 +3137,7 @@ async function taskStatus() {
2550
3137
  const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/status`);
2551
3138
  if (!response.ok) {
2552
3139
  const rawBody = await response.text();
2553
- if (hasArg('--json')) {
3140
+ if (hasJsonFlag()) {
2554
3141
  let errorPayload = { error: rawBody };
2555
3142
  try {
2556
3143
  const parsed = JSON.parse(rawBody);
@@ -2577,7 +3164,7 @@ async function taskStatus() {
2577
3164
  throw new Error(`Failed to fetch task status: ${errorMsg}`);
2578
3165
  }
2579
3166
  const data = await parseJsonResponse(response, 'Task status');
2580
- if (hasArg('--json')) {
3167
+ if (hasJsonFlag()) {
2581
3168
  printLine(JSON.stringify(data, null, 2));
2582
3169
  return;
2583
3170
  }
@@ -2599,6 +3186,10 @@ async function updateTaskStatus(targetStatus) {
2599
3186
  throw new Error(`Failed to ${verb} task: ${await response.text()}`);
2600
3187
  }
2601
3188
  const data = await parseJsonResponse(response, 'Task status update');
3189
+ if (hasJsonFlag()) {
3190
+ printJson({ task: data.task });
3191
+ return;
3192
+ }
2602
3193
  const action = targetStatus === 'paused' ? 'Paused' : 'Unpaused';
2603
3194
  printLine(`${action} task ${sanitizeTerminalText(data.task.id)} [${sanitizeTerminalText(data.task.status)}]`);
2604
3195
  }
@@ -2656,7 +3247,7 @@ async function appDetail() {
2656
3247
  if (!detail) {
2657
3248
  throw new Error(`App '${appId}' not found in project '${projectSlug}'`);
2658
3249
  }
2659
- if (hasArg('--json')) {
3250
+ if (hasJsonFlag()) {
2660
3251
  printLine(JSON.stringify(detail, null, 2));
2661
3252
  return;
2662
3253
  }
@@ -2709,11 +3300,24 @@ async function exportAppSource() {
2709
3300
  ? expandHomePath(outPathArg)
2710
3301
  : defaultAppExportFilename(data.app.name, data.version.versionNum);
2711
3302
  writeTextFileEnsuringDir(filename, data.version.code);
3303
+ if (hasJsonFlag()) {
3304
+ printJson({
3305
+ app: { id: data.app.id, name: data.app.name },
3306
+ versionNum: data.version.versionNum,
3307
+ savedTo: filename,
3308
+ });
3309
+ return;
3310
+ }
2712
3311
  printLine(`Saved app ${sanitizeTerminalText(data.app.name)} v${data.version.versionNum} source to ${sanitizeTerminalText(filename)}`);
2713
3312
  }
2714
3313
  async function listTeamMembers() {
2715
3314
  const teamSlug = await resolveTeamSlug(getArg('--team'));
2716
- printTeamMembers(await fetchTeamMembers(teamSlug));
3315
+ const members = await fetchTeamMembers(teamSlug);
3316
+ if (hasJsonFlag()) {
3317
+ printJson({ team: teamSlug, members });
3318
+ return;
3319
+ }
3320
+ printTeamMembers(members);
2717
3321
  }
2718
3322
  async function addTeamMember() {
2719
3323
  const teamSlug = await resolveTeamSlug(getArg('--team'));
@@ -2730,6 +3334,10 @@ async function addTeamMember() {
2730
3334
  throw new Error(`Failed to add team member: ${await response.text()}`);
2731
3335
  }
2732
3336
  const data = await parseJsonResponse(response, 'Team member add');
3337
+ if (hasJsonFlag()) {
3338
+ printJson({ member: data.member });
3339
+ return;
3340
+ }
2733
3341
  printLine(`Added team member ${sanitizeTerminalText(data.member.email)} (${sanitizeTerminalText(data.member.id)})`);
2734
3342
  }
2735
3343
  async function removeTeamMember() {
@@ -2747,6 +3355,10 @@ async function removeTeamMember() {
2747
3355
  if (!response.ok) {
2748
3356
  throw new Error(`Failed to remove team member: ${await response.text()}`);
2749
3357
  }
3358
+ if (hasJsonFlag()) {
3359
+ printJson({ removed: { id: member.id, email: member.email } });
3360
+ return;
3361
+ }
2750
3362
  printLine(`Removed team member ${sanitizeTerminalText(member.email)}`);
2751
3363
  }
2752
3364
  async function changeTeamMemberRole() {
@@ -2773,6 +3385,10 @@ async function changeTeamMemberRole() {
2773
3385
  if (!response.ok) {
2774
3386
  throw new Error(`Failed to update member role: ${await response.text()}`);
2775
3387
  }
3388
+ if (hasJsonFlag()) {
3389
+ printJson({ member: { id: member.id, email: member.email, role } });
3390
+ return;
3391
+ }
2776
3392
  printLine(`Updated ${sanitizeTerminalText(member.email)} role to ${sanitizeTerminalText(role)}`);
2777
3393
  }
2778
3394
  async function createDatasetFromRows(project, name, sourceType, rows, readmeMarkdown = null) {
@@ -2849,6 +3465,10 @@ async function uploadJsonlDatasetInChunks(file, project, datasetName, readmeMark
2849
3465
  if (!dataset) {
2850
3466
  throw new Error('Dataset file contains no rows');
2851
3467
  }
3468
+ if (hasJsonFlag()) {
3469
+ printJson({ dataset });
3470
+ return;
3471
+ }
2852
3472
  printLine(`Uploaded dataset ${sanitizeTerminalText(dataset.name)} (${sanitizeTerminalText(dataset.id)}) with ${dataset.rowCount} rows.`);
2853
3473
  if (dataset.url) {
2854
3474
  printLine(`View dataset: ${formatTerminalLink(dataset.url)}`);
@@ -2872,6 +3492,10 @@ async function uploadDataset() {
2872
3492
  }
2873
3493
  const { rows, sourceType } = parseDatasetFile(file);
2874
3494
  const data = await createDatasetFromRows(project, datasetName, sourceType, rows, readmeMarkdown);
3495
+ if (hasJsonFlag()) {
3496
+ printJson({ dataset: data.dataset });
3497
+ return;
3498
+ }
2875
3499
  printLine(`Uploaded dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) with ${data.dataset.rowCount} rows.`);
2876
3500
  if (data.dataset.url) {
2877
3501
  printLine(`View dataset: ${formatTerminalLink(data.dataset.url)}`);
@@ -2976,6 +3600,10 @@ async function downloadDataset() {
2976
3600
  : `${datasetId}.${format}`;
2977
3601
  const bytes = new Uint8Array(await response.arrayBuffer());
2978
3602
  writeFileSync(filename, bytes);
3603
+ if (hasJsonFlag()) {
3604
+ printJson({ datasetId, format, savedTo: filename });
3605
+ return;
3606
+ }
2979
3607
  printLine(`Saved dataset ${sanitizeTerminalText(datasetId)} (${format.toUpperCase()}) to ${sanitizeTerminalText(filename)}`);
2980
3608
  }
2981
3609
  const MAX_INPUT_FILE_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB
@@ -3101,6 +3729,10 @@ async function appendDatasetRows() {
3101
3729
  }
3102
3730
  }
3103
3731
  if (lastResult) {
3732
+ if (hasJsonFlag()) {
3733
+ printJson({ dataset: lastResult.dataset, appendedCount: totalAppended });
3734
+ return;
3735
+ }
3104
3736
  printLine(`Appended ${totalAppended} rows to dataset ${sanitizeTerminalText(lastResult.dataset.name)} (${sanitizeTerminalText(lastResult.dataset.id)}). New row count: ${lastResult.dataset.rowCount}`);
3105
3737
  }
3106
3738
  }
@@ -3147,6 +3779,10 @@ async function editDatasetRows() {
3147
3779
  throw new Error(`Edit rows failed: ${await response.text()}`);
3148
3780
  }
3149
3781
  const data = await parseJsonResponse(response, 'Dataset edit rows');
3782
+ if (hasJsonFlag()) {
3783
+ printJson({ dataset: data.dataset, updatedCount: data.updatedCount });
3784
+ return;
3785
+ }
3150
3786
  printLine(`Updated ${data.updatedCount} rows in dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Current row count: ${data.dataset.rowCount}`);
3151
3787
  }
3152
3788
  async function deleteDatasetRows() {
@@ -3175,6 +3811,10 @@ async function deleteDatasetRows() {
3175
3811
  throw new Error(`Delete rows failed: ${await response.text()}`);
3176
3812
  }
3177
3813
  const data = await parseJsonResponse(response, 'Dataset delete rows');
3814
+ if (hasJsonFlag()) {
3815
+ printJson({ dataset: data.dataset, deletedCount: data.deletedCount });
3816
+ return;
3817
+ }
3178
3818
  printLine(`Deleted ${data.deletedCount} rows from dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). New row count: ${data.dataset.rowCount}`);
3179
3819
  }
3180
3820
  async function confirmDatasetDeletion(dataset) {
@@ -3213,6 +3853,10 @@ async function deleteDataset() {
3213
3853
  throw new Error(`Delete failed: ${await response.text()}`);
3214
3854
  }
3215
3855
  const data = await parseJsonResponse(response, 'Dataset delete');
3856
+ if (hasJsonFlag()) {
3857
+ printJson({ dataset: data.dataset, deleted: true });
3858
+ return;
3859
+ }
3216
3860
  printLine(`Deleted dataset ${sanitizeTerminalText(data.dataset.id)}.`);
3217
3861
  }
3218
3862
  async function lockDataset() {
@@ -3236,6 +3880,10 @@ async function lockDataset() {
3236
3880
  throw new Error(`Lock failed: ${await response.text()}`);
3237
3881
  }
3238
3882
  const data = await parseJsonResponse(response, 'Dataset lock');
3883
+ if (hasJsonFlag()) {
3884
+ printJson({ dataset: data.dataset });
3885
+ return;
3886
+ }
3239
3887
  printLine(`Locked dataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) at ${sanitizeTerminalText(data.dataset.lockedAt)}. Row count: ${data.dataset.rowCount}`);
3240
3888
  }
3241
3889
  async function cloneDataset() {
@@ -3259,6 +3907,10 @@ async function cloneDataset() {
3259
3907
  throw new Error(`Clone failed: ${await response.text()}`);
3260
3908
  }
3261
3909
  const data = await parseJsonResponse(response, 'Dataset clone');
3910
+ if (hasJsonFlag()) {
3911
+ printJson({ dataset: data.dataset });
3912
+ return;
3913
+ }
3262
3914
  printLine(`Cloned dataset ${sanitizeTerminalText(data.dataset.parentDatasetId)} -> ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}). Row count: ${data.dataset.rowCount}`);
3263
3915
  }
3264
3916
  async function downloadAnnotations() {
@@ -3281,6 +3933,10 @@ async function downloadAnnotations() {
3281
3933
  : fallbackName;
3282
3934
  const bytes = new Uint8Array(await response.arrayBuffer());
3283
3935
  writeFileSync(filename, bytes);
3936
+ if (hasJsonFlag()) {
3937
+ printJson({ taskId, format, savedTo: filename });
3938
+ return;
3939
+ }
3284
3940
  printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`);
3285
3941
  }
3286
3942
  function readRunnerManifest(runnerDir) {
@@ -3493,7 +4149,12 @@ async function runnersExec() {
3493
4149
  else {
3494
4150
  writeFileSync(outPath, resultJsonl);
3495
4151
  }
3496
- printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`);
4152
+ if (hasJsonFlag()) {
4153
+ printJson({ rowCount: context.rows.length, out: outPath });
4154
+ }
4155
+ else {
4156
+ printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`);
4157
+ }
3497
4158
  }
3498
4159
  finally {
3499
4160
  materializedRunner.cleanup();
@@ -3527,6 +4188,9 @@ export async function main(rawArgs = process.argv.slice(2)) {
3527
4188
  }
3528
4189
  const helpTarget = extractHelpTarget(cliArgs);
3529
4190
  if (helpTarget !== null) {
4191
+ if (helpTarget.length === 0) {
4192
+ printBannerIfInteractive();
4193
+ }
3530
4194
  printLine(renderHelpForArgs(helpTarget));
3531
4195
  return;
3532
4196
  }
@@ -3544,6 +4208,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
3544
4208
  printCapabilities();
3545
4209
  return;
3546
4210
  }
4211
+ if (command === 'setup' && subcommand === 'prompt') {
4212
+ setupPromptCommand();
4213
+ return;
4214
+ }
4215
+ if (command === 'setup') {
4216
+ await setupCommand();
4217
+ return;
4218
+ }
3547
4219
  if (command === 'install-skill') {
3548
4220
  await installSkillCommand();
3549
4221
  return;
@@ -3552,6 +4224,18 @@ export async function main(rawArgs = process.argv.slice(2)) {
3552
4224
  await installSkillCommand();
3553
4225
  return;
3554
4226
  }
4227
+ if (command === 'skills' && subcommand === 'path') {
4228
+ skillsPathCommand();
4229
+ return;
4230
+ }
4231
+ if (command === 'skills' && subcommand === 'status') {
4232
+ skillsStatusCommand();
4233
+ return;
4234
+ }
4235
+ if (command === 'skills' && subcommand === 'update') {
4236
+ await skillsUpdateCommand();
4237
+ return;
4238
+ }
3555
4239
  if (command === 'login') {
3556
4240
  await login();
3557
4241
  return;
@@ -3621,6 +4305,14 @@ export async function main(rawArgs = process.argv.slice(2)) {
3621
4305
  await pushPromptArtifact('prompt');
3622
4306
  return;
3623
4307
  }
4308
+ if (command === 'prompts' && subcommand === 'archive') {
4309
+ await setPromptArchivedState(true);
4310
+ return;
4311
+ }
4312
+ if (command === 'prompts' && subcommand === 'restore') {
4313
+ await setPromptArchivedState(false);
4314
+ return;
4315
+ }
3624
4316
  if (command === 'prompts' && subcommand === 'labels' && cliArgs[2] === 'set') {
3625
4317
  await setPromptLabel();
3626
4318
  return;