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/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
- printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)}`);
212
- printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)}`);
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 response = await authedFetch(`/api/cli/prompts?project=${encodeURIComponent(project)}`);
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 response = await authedFetch(`/api/cli/judges?project=${encodeURIComponent(project)}`);
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
- printTeams(await fetchTeams());
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
- printProjects(await fetchProjects(teamSlug || undefined));
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
- printTasks(await fetchTasks(project || undefined));
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
- printApps(await fetchApps(project));
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
- async function promptSkillInstallTargets() {
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(`Usage: orizu install-skill --target <${SKILL_INSTALL_TARGETS.join('|')}> [--yes] [--dry-run]`);
2135
+ throw new Error(SKILL_INSTALL_USAGE);
1992
2136
  }
1993
- printLine('Where should the Orizu CLI skill be installed?');
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 target of SKILL_INSTALL_TARGETS) {
1996
- const defaultYes = target === 'agent-user';
1997
- if (await askYesNo(` Install to ${target}?`, defaultYes)) {
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 parseSkillInstallTargets() {
2004
- const rawTargets = getArgs('--target');
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 target of rawTargets) {
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
- 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();
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 (targets.length === 0) {
2033
- printLine('No targets selected; nothing to do.');
2034
- return;
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 (!dryRun && !overwrite && targetNeedsOverwrite(target)) {
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
- printLine(`Skipped ${installPath}`);
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
- dryRun,
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
- printLine(`${formatSkillInstallAction(result.action)} ${result.path}`);
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 labelsPerItem = labelsPerItemArg ? Number(labelsPerItemArg) : 1;
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 (!projectSlug || !datasetId || !appId || !title || (publish && assignees.length === 0)) {
2420
- throw new Error('Usage: orizu tasks create --project <team/project> --dataset <datasetId> --app <appId> --title <title> [--assignees <userIdOrEmail1,userIdOrEmail2>] [--publish] [--version <n>] [--instructions <text>] [--labels-per-item <n>] [--json]');
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 (hasArg('--json')) {
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 (hasArg('--json')) {
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: `orizu tasks publish --task ${data.task.id} --assignees <userId1,userId2>`,
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: orizu tasks publish --task ${sanitizeTerminalText(data.task.id)} --assignees <userId1,userId2>`
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
- if (!taskId || assignees.length === 0) {
2494
- throw new Error('Usage: orizu tasks publish --task <taskId> --assignees <userId1,userId2> [--json]');
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({ memberIds: assignees }),
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 (hasArg('--json')) {
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
- if (!taskId || assignees.length === 0) {
2532
- throw new Error('Usage: orizu tasks assign --task <taskId> --assignees <userId1,userId2>');
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({ memberIds: assignees }),
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
- printLine(`Created ${data.assignmentsCreated} assignments.`);
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 (hasArg('--json')) {
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 (hasArg('--json')) {
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 (hasArg('--json')) {
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
- printTeamMembers(await fetchTeamMembers(teamSlug));
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
- printLine(`Wrote ${context.rows.length} runner results to ${sanitizeTerminalText(outPath)}`);
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;