orizu 0.5.31 → 0.5.32

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.
Files changed (47) hide show
  1. package/dist/app-commands.d.ts +2 -0
  2. package/dist/app-commands.d.ts.map +1 -1
  3. package/dist/app-commands.js +10 -4
  4. package/dist/help.js +5 -5
  5. package/dist/hosted-agent-prompt.d.ts +14 -0
  6. package/dist/hosted-agent-prompt.d.ts.map +1 -0
  7. package/dist/hosted-agent-prompt.js +17 -0
  8. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  9. package/dist/hosted-harness-opencode.js +4 -0
  10. package/dist/hosted-optimization-prebaked-marker.d.ts +9 -0
  11. package/dist/hosted-optimization-prebaked-marker.d.ts.map +1 -0
  12. package/dist/hosted-optimization-prebaked-marker.js +22 -0
  13. package/dist/markdown-report-input.d.ts.map +1 -1
  14. package/dist/markdown-report-input.js +1 -1
  15. package/dist/preview-runtime.d.ts +7 -0
  16. package/dist/preview-runtime.d.ts.map +1 -1
  17. package/dist/preview-runtime.js +49 -5
  18. package/dist/provider-refusals.js +2 -2
  19. package/dist/report-markdown-blankness.d.ts +6 -0
  20. package/dist/report-markdown-blankness.d.ts.map +1 -1
  21. package/dist/report-markdown-blankness.js +47 -4
  22. package/dist/setup-and-skills-commands.d.ts.map +1 -1
  23. package/dist/setup-and-skills-commands.js +123 -9
  24. package/dist/skill-installer.d.ts +32 -0
  25. package/dist/skill-installer.d.ts.map +1 -1
  26. package/dist/skill-installer.js +228 -1
  27. package/package.json +1 -1
  28. package/src/app-commands.ts +13 -3
  29. package/src/help.ts +5 -5
  30. package/src/hosted-agent-prompt.ts +18 -0
  31. package/src/hosted-harness-opencode.ts +4 -0
  32. package/src/hosted-optimization-prebaked-marker.ts +20 -0
  33. package/src/markdown-report-input.ts +3 -1
  34. package/src/preview-runtime.ts +44 -5
  35. package/src/provider-refusals.ts +2 -2
  36. package/src/report-markdown-blankness.ts +59 -4
  37. package/src/setup-and-skills-commands.ts +160 -8
  38. package/src/skill-installer.ts +315 -0
  39. package/vendor/skills/orizu/SKILL.md +3 -3
  40. package/vendor/skills/orizu/flows/first-win.md +3 -1
  41. package/vendor/skills/orizu/flows/promote.md +2 -1
  42. package/vendor/skills/orizu/references/building-apps.md +2 -0
  43. package/vendor/skills/orizu/references/building-judges.md +138 -8
  44. package/vendor/skills/orizu/references/cli-reference.md +1 -1
  45. package/vendor/skills/orizu/references/optimization-with-gepa.md +21 -3
  46. package/vendor/skills/orizu/references/primer.md +2 -2
  47. package/vendor/skills/orizu/scripts/test-app.mjs +53 -4
@@ -9,7 +9,7 @@ import { spawnSync } from 'child_process';
9
9
  import { isInteractiveTerminal, getArg, getArgs, hasJsonFlag, hasArg, normalizeSlugInput, getOptionalArgValue, expandHomePath, cliArgs, } from './command-line-arguments.js';
10
10
  import { printLine, getCliVersion, getErrorMessage, printJson, printBannerIfInteractive, } from './cli-console-output.js';
11
11
  import { sanitizeTerminalText } from './json-response.js';
12
- import { SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, getTargetForAgent, getSkillInstallPath, isSkillInstallAgent, isSkillInstallTarget, installSkillTarget, targetNeedsOverwrite, resolveSkillSource, getSkillTargetStatus, computeSkillContentHash, } from './skill-installer.js';
12
+ import { SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, getTargetForAgent, getSkillInstallPath, isSkillInstallAgent, isSkillInstallTarget, installSkillTarget, targetNeedsOverwrite, resolveSkillSource, getSkillTargetStatus, computeSkillContentHash, findLegacySkillInstalls, replaceLegacySkillInstalls, } from './skill-installer.js';
13
13
  import { createTeamOnServer, fetchTeams, fetchProjects, createProjectOnServer, } from './account-directory.js';
14
14
  import { authedFetch, captureAuthenticatedRequestContext } from './http.js';
15
15
  import { promptKeyboardSelect } from './keyboard-select.js';
@@ -74,7 +74,7 @@ async function waitForAnyKeyOrEscape(message) {
74
74
  rawInput.setRawMode?.(true);
75
75
  });
76
76
  }
77
- async function promptKeyboardMultiSelect(title, items, label, defaultSelected) {
77
+ async function promptKeyboardMultiSelect(title, items, label, defaultSelected, options = {}) {
78
78
  if (items.length === 0) {
79
79
  return [];
80
80
  }
@@ -82,7 +82,7 @@ async function promptKeyboardMultiSelect(title, items, label, defaultSelected) {
82
82
  throw new Error(`${title} selection requires interactive terminal. Provide flags explicitly instead.`);
83
83
  }
84
84
  printLine(`\n${sanitizeTerminalText(title)}`);
85
- printLine('Use ↑/↓ to move, Space to toggle, Enter to confirm, or Esc to skip.');
85
+ printLine(options.hint ?? 'Use ↑/↓ to move, Space to toggle, Enter to confirm, or Esc to skip.');
86
86
  let selected = 0;
87
87
  let rendered = false;
88
88
  const checked = new Set();
@@ -126,7 +126,7 @@ async function promptKeyboardMultiSelect(title, items, label, defaultSelected) {
126
126
  }
127
127
  if (key.name === 'escape') {
128
128
  cleanup();
129
- resolvePromise([]);
129
+ resolvePromise(options.escapeKeepsDefaults ? items.filter((item, index) => defaultSelected(item, index)) : []);
130
130
  return;
131
131
  }
132
132
  if (key.name === 'up') {
@@ -423,9 +423,80 @@ async function installSkillCommand() {
423
423
  return;
424
424
  }
425
425
  const outcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
426
+ const legacy = replaceLegacySkillInstalls({
427
+ ...legacyScopeAfterInstall(targets, outcomes, dryRun),
428
+ mode,
429
+ dryRun,
430
+ cliVersion: getCliVersion(),
431
+ });
426
432
  if (hasJsonFlag()) {
427
- printJson({ dryRun, installs: outcomes });
433
+ printJson({ dryRun, installs: outcomes, legacy: legacy.replacements, legacyProblems: legacy.problems });
434
+ return;
435
+ }
436
+ printLegacyReport(legacy);
437
+ }
438
+ // install-skill and setup only touch old installs in the targets the user
439
+ // chose. A chosen target whose own install was skipped or failed keeps its
440
+ // old install, with that reason.
441
+ function legacyScopeAfterInstall(targets, outcomes, dryRun) {
442
+ const heldBack = {};
443
+ if (!dryRun) {
444
+ for (const outcome of outcomes) {
445
+ if (outcome.action === 'skipped') {
446
+ heldBack[outcome.target] = 'you chose not to replace the current skill there';
447
+ }
448
+ else if (outcome.action === 'failed') {
449
+ heldBack[outcome.target] = `the current skill could not be installed there: ${outcome.error}`;
450
+ }
451
+ }
452
+ }
453
+ return { targets, heldBack };
454
+ }
455
+ function printLegacyReport(report) {
456
+ for (const line of describeLegacyReport(report)) {
457
+ printLine(sanitizeSetupHumanInlineText(line));
458
+ }
459
+ }
460
+ function describeLegacyReplacement(replacement) {
461
+ const moved = replacement.backupPath;
462
+ if (replacement.action === 'replaced') {
463
+ return `Replaced old Orizu skill ${replacement.path} with ${replacement.installedPath}`
464
+ + (moved ? `; moved the old copy to ${moved}` : '');
465
+ }
466
+ if (replacement.action === 'removed') {
467
+ return `Removed old Orizu skill ${replacement.path}` + (moved ? `; moved it to ${moved}` : '');
468
+ }
469
+ if (replacement.action === 'would-replace') {
470
+ return `Would replace old Orizu skill ${replacement.path} with ${replacement.installedPath}`;
471
+ }
472
+ if (replacement.action === 'would-remove') {
473
+ return `Would remove old Orizu skill ${replacement.path}`;
474
+ }
475
+ if (replacement.action === 'left-in-place') {
476
+ return `Left old Orizu skill ${replacement.path} in place (${replacement.reason}). Run \`orizu skills update\` to replace it.`;
428
477
  }
478
+ if (replacement.installedPath) {
479
+ return `Installed the current skill at ${replacement.installedPath}, but could not move old Orizu skill ${replacement.path}: ${replacement.error}. The old copy is still in place.`;
480
+ }
481
+ return `Could not replace old Orizu skill ${replacement.path}: ${replacement.error}`;
482
+ }
483
+ function describeLegacyProblem(problem) {
484
+ return `Could not check ${problem.path}: ${problem.error}`;
485
+ }
486
+ function describeLegacyReport(report) {
487
+ return [
488
+ ...report.replacements.map(describeLegacyReplacement),
489
+ ...report.problems.map(describeLegacyProblem),
490
+ ];
491
+ }
492
+ function describeLegacyInstall(install) {
493
+ if (install.kind === 'broken-link') {
494
+ return `outdated link to ${install.linkTarget}, which no longer exists`;
495
+ }
496
+ if (install.kind === 'link') {
497
+ return `outdated link to ${install.linkTarget}`;
498
+ }
499
+ return 'outdated copy';
429
500
  }
430
501
  function describeSkillTargetState(status) {
431
502
  if (status.state === 'current')
@@ -441,6 +512,7 @@ function describeSkillTargetState(status) {
441
512
  function skillsStatusCommand() {
442
513
  const source = resolveSkillSource();
443
514
  const statuses = SKILL_INSTALL_TARGETS.map(target => getSkillTargetStatus(target));
515
+ const { installs: legacyInstalls, problems: legacyProblems } = findLegacySkillInstalls();
444
516
  if (hasJsonFlag()) {
445
517
  printJson({
446
518
  name: source.name,
@@ -457,6 +529,8 @@ function skillsStatusCommand() {
457
529
  installedHash: status.installedHash,
458
530
  meta: status.meta,
459
531
  })),
532
+ legacyInstalls,
533
+ legacyProblems,
460
534
  });
461
535
  return;
462
536
  }
@@ -467,11 +541,27 @@ function skillsStatusCommand() {
467
541
  printLine(` ${status.target.padEnd(16)} ${describeSkillTargetState(status)}${mode}`);
468
542
  printLine(` ${''.padEnd(16)} ${status.path}`);
469
543
  }
544
+ if (legacyInstalls.length > 0) {
545
+ printLine('');
546
+ printLine('Old Orizu skill installs (outdated; agents may still load these):');
547
+ for (const install of legacyInstalls) {
548
+ printLine(sanitizeSetupHumanInlineText(` ${install.name.padEnd(16)} ${describeLegacyInstall(install)}`));
549
+ printLine(sanitizeSetupHumanInlineText(` ${''.padEnd(16)} ${install.path}`));
550
+ }
551
+ printLine('Run `orizu skills update` to replace them.');
552
+ }
553
+ if (legacyProblems.length > 0) {
554
+ printLine('');
555
+ for (const problem of legacyProblems) {
556
+ printLine(sanitizeSetupHumanInlineText(describeLegacyProblem(problem)));
557
+ }
558
+ }
470
559
  }
471
560
  async function skillsUpdateCommand() {
472
561
  const dryRun = hasArg('--dry-run');
473
562
  const cliVersion = getCliVersion();
474
563
  const updates = [];
564
+ const legacy = replaceLegacySkillInstalls({ dryRun, cliVersion });
475
565
  for (const target of SKILL_INSTALL_TARGETS) {
476
566
  const status = getSkillTargetStatus(target);
477
567
  if (status.state === 'missing' || status.state === 'unmanaged') {
@@ -498,10 +588,11 @@ async function skillsUpdateCommand() {
498
588
  });
499
589
  }
500
590
  if (hasJsonFlag()) {
501
- printJson({ updates });
591
+ printJson({ updates, legacy: legacy.replacements, legacyProblems: legacy.problems });
502
592
  return;
503
593
  }
504
- if (updates.length === 0) {
594
+ printLegacyReport(legacy);
595
+ if (updates.length === 0 && legacy.replacements.length === 0 && legacy.problems.length === 0) {
505
596
  printLine('No Orizu skill installs found. Run `orizu install-skill` first.');
506
597
  return;
507
598
  }
@@ -605,7 +696,7 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
605
696
  throw new Error('--create-project requires a non-empty project name.');
606
697
  const selected = await createProjectOnServer(team.slug, name, request);
607
698
  printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`);
608
- return { selected, projects: [...projects, selected] };
699
+ return { selected, projects: [selected] };
609
700
  }
610
701
  const normalizedProjectSlug = projectSlugArg ? normalizeSlugInput(projectSlugArg) : null;
611
702
  if (normalizedProjectSlug) {
@@ -613,7 +704,7 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
613
704
  if (!selected) {
614
705
  throw new Error(`Project '${team.slug}/${normalizedProjectSlug}' was not found.`);
615
706
  }
616
- return { selected, projects };
707
+ return { selected, projects: [selected] };
617
708
  }
618
709
  if (projects.length === 0) {
619
710
  if (!canCreateProject)
@@ -643,6 +734,17 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
643
734
  printLine(`Created project ${sanitizeTerminalText(`${selected.teamSlug}/${selected.slug}`)}`);
644
735
  return { selected, projects: [...projects, selected] };
645
736
  }
737
+ // Every project starts checked and Esc keeps them all. The active project is
738
+ // always set up, so Enter with nothing checked sets up only that one.
739
+ async function chooseProjectsToScaffold(projects, active) {
740
+ const checked = await promptKeyboardMultiSelect('Choose the team projects to set up in this workspace', projects, project => project.slug === active.slug
741
+ ? `${project.name} (${project.slug}) — active project, always set up`
742
+ : `${project.name} (${project.slug})`, () => true, {
743
+ hint: 'Use ↑/↓ to move, Space to toggle, Enter to confirm, or Esc to keep them all. With nothing checked, only the active project is set up.',
744
+ escapeKeepsDefaults: true,
745
+ });
746
+ return projects.filter(project => project.slug === active.slug || checked.includes(project));
747
+ }
646
748
  function projectSeedsFromProjects(projects) {
647
749
  return projects.map(project => ({
648
750
  slug: project.slug,
@@ -789,6 +891,9 @@ async function setupCommand() {
789
891
  setupProjects = selection.projects;
790
892
  printLine(`Selected project ${sanitizeTerminalText(`${setupTeam.slug}/${selectedProject.slug}`)}`);
791
893
  printLine('');
894
+ if (!noInput && setupProjects.length > 1 && !hasArg('--no-workspace')) {
895
+ setupProjects = await chooseProjectsToScaffold(setupProjects, selectedProject);
896
+ }
792
897
  }
793
898
  }
794
899
  // Step 2: local workspace
@@ -902,6 +1007,15 @@ async function setupCommand() {
902
1007
  if (dryRun) {
903
1008
  printLine('Dry run: no skill files were changed.');
904
1009
  }
1010
+ const legacy = replaceLegacySkillInstalls({
1011
+ ...legacyScopeAfterInstall(targets, installOutcomes, dryRun),
1012
+ mode: installMode,
1013
+ dryRun,
1014
+ cwd: workspaceRoot,
1015
+ homeDir: setupSkillHome,
1016
+ cliVersion: getCliVersion(),
1017
+ });
1018
+ printLegacyReport(legacy);
905
1019
  for (const outcome of installOutcomes) {
906
1020
  const label = setupSkillLabel(outcome.target);
907
1021
  if (outcome.action === 'failed') {
@@ -65,4 +65,36 @@ export interface SkillTargetStatus {
65
65
  }
66
66
  export declare function getSkillTargetStatus(target: SkillInstallTarget, options?: SkillInstallOptions): SkillTargetStatus;
67
67
  export declare function isSkillInstallTarget(value: string): value is SkillInstallTarget;
68
+ export interface LegacySkillInstall {
69
+ target: SkillInstallTarget;
70
+ name: string;
71
+ path: string;
72
+ kind: 'directory' | 'link' | 'broken-link';
73
+ linkTarget: string | null;
74
+ resolvedPath: string | null;
75
+ }
76
+ export interface LegacySkillProblem {
77
+ path: string;
78
+ error: string;
79
+ }
80
+ export interface LegacySkillScan {
81
+ installs: LegacySkillInstall[];
82
+ problems: LegacySkillProblem[];
83
+ }
84
+ export interface LegacySkillReplacement extends LegacySkillInstall {
85
+ action: 'replaced' | 'removed' | 'would-replace' | 'would-remove' | 'left-in-place' | 'failed';
86
+ installedPath: string | null;
87
+ backupPath: string | null;
88
+ reason: string | null;
89
+ error: string | null;
90
+ }
91
+ export interface LegacySkillReplaceOptions extends SkillInstallOptions {
92
+ targets?: readonly SkillInstallTarget[];
93
+ heldBack?: Partial<Record<SkillInstallTarget, string>>;
94
+ }
95
+ export declare function findLegacySkillInstalls(options?: SkillInstallOptions): LegacySkillScan;
96
+ export declare function replaceLegacySkillInstalls(options?: LegacySkillReplaceOptions): {
97
+ replacements: LegacySkillReplacement[];
98
+ problems: LegacySkillProblem[];
99
+ };
68
100
  //# sourceMappingURL=skill-installer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skill-installer.d.ts","sourceRoot":"","sources":["../src/skill-installer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skill-installer.d.ts","sourceRoot":"","sources":["../src/skill-installer.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,13 @@
1
- import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, symlinkSync, writeFileSync, } from 'fs';
1
+ import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from 'fs';
2
2
  import { dirname, isAbsolute, join, relative, resolve } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { homedir } from 'os';
5
5
  import { createHash } from 'crypto';
6
6
  import { workspaceExists } from './workspace.js';
7
7
  export const SKILL_NAME = 'orizu';
8
+ // The name earlier releases installed the skill under. Agents still load
9
+ // these old copies, so status, update, install and setup find and replace them.
10
+ const LEGACY_SKILL_NAME = 'orizu-cli';
8
11
  export const AGENTS_START_MARKER = '<!-- orizu-cli:start -->';
9
12
  export const AGENTS_END_MARKER = '<!-- orizu-cli:end -->';
10
13
  export const SKILL_INSTALL_TARGETS = [
@@ -463,3 +466,227 @@ export function getSkillTargetStatus(target, options) {
463
466
  export function isSkillInstallTarget(value) {
464
467
  return SKILL_INSTALL_TARGETS.includes(value);
465
468
  }
469
+ function getLegacySkillInstallPath(target, options) {
470
+ return resolve(dirname(getSkillInstallPath(target, options)), LEGACY_SKILL_NAME);
471
+ }
472
+ // The closing delimiter must be a whole `---` line.
473
+ const FRONTMATTER_BLOCK = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
474
+ function readFrontmatterName(markdown) {
475
+ const frontmatter = markdown.match(FRONTMATTER_BLOCK);
476
+ if (!frontmatter) {
477
+ return null;
478
+ }
479
+ const name = frontmatter[1].match(/^name:\s*['"]?([^'"\r\n]*?)['"]?\s*$/m);
480
+ return name ? name[1] : null;
481
+ }
482
+ function stripFrontmatterName(markdown) {
483
+ return markdown.replace(FRONTMATTER_BLOCK, frontmatter => frontmatter.replace(/^name:.*$/m, ''));
484
+ }
485
+ // Only a folder that is positively an old Orizu skill counts: its SKILL.md
486
+ // header carries the old name and the rest of the file names Orizu. Every
487
+ // past install, ours or a hand copy, has that SKILL.md. A user's own folder
488
+ // that happens to share the name is never reported or touched.
489
+ function isOrizuLegacySkillDir(dir) {
490
+ const skillMd = join(dir, 'SKILL.md');
491
+ if (!existsSync(skillMd)) {
492
+ return false;
493
+ }
494
+ const markdown = readFileSync(skillMd, 'utf8');
495
+ return readFrontmatterName(markdown) === LEGACY_SKILL_NAME
496
+ && /orizu/i.test(stripFrontmatterName(markdown));
497
+ }
498
+ function inspectLegacySkillPath(target, path) {
499
+ let stats;
500
+ try {
501
+ stats = lstatSync(path);
502
+ }
503
+ catch {
504
+ return null;
505
+ }
506
+ const base = { target, name: LEGACY_SKILL_NAME, path };
507
+ if (stats.isSymbolicLink()) {
508
+ const linkTarget = readlinkSync(path);
509
+ if (!existsSync(path)) {
510
+ // The folder behind it is gone, so there is no SKILL.md to read. Every
511
+ // past installer linked to a folder called skills/<old name>.
512
+ return linkTarget.replace(/\\/g, '/').replace(/\/+$/, '').endsWith(`skills/${LEGACY_SKILL_NAME}`)
513
+ ? { ...base, kind: 'broken-link', linkTarget, resolvedPath: null }
514
+ : null;
515
+ }
516
+ const resolvedPath = realpathSync(path);
517
+ return isOrizuLegacySkillDir(resolvedPath)
518
+ ? { ...base, kind: 'link', linkTarget, resolvedPath }
519
+ : null;
520
+ }
521
+ return stats.isDirectory() && isOrizuLegacySkillDir(path)
522
+ ? { ...base, kind: 'directory', linkTarget: null, resolvedPath: null }
523
+ : null;
524
+ }
525
+ function errorMessage(error) {
526
+ return error instanceof Error ? error.message : String(error);
527
+ }
528
+ function errorCode(error) {
529
+ return error && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
530
+ }
531
+ export function findLegacySkillInstalls(options) {
532
+ const installs = [];
533
+ const problems = [];
534
+ const seenPaths = new Set();
535
+ for (const target of SKILL_INSTALL_TARGETS) {
536
+ if (target === 'agents-md') {
537
+ continue;
538
+ }
539
+ const path = getLegacySkillInstallPath(target, options);
540
+ if (seenPaths.has(path)) {
541
+ continue;
542
+ }
543
+ seenPaths.add(path);
544
+ try {
545
+ const install = inspectLegacySkillPath(target, path);
546
+ if (install) {
547
+ installs.push(install);
548
+ }
549
+ }
550
+ catch (error) {
551
+ problems.push({ path, error: errorMessage(error) });
552
+ }
553
+ }
554
+ return { installs, problems };
555
+ }
556
+ function backupDirectoryFor(options) {
557
+ return resolve(resolveHome(options), '.orizu', 'skill-backups');
558
+ }
559
+ function uniqueBackupPath(install, options) {
560
+ const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
561
+ const base = join(backupDirectoryFor(options), `${LEGACY_SKILL_NAME}-${stamp}-${install.target}`);
562
+ let candidate = base;
563
+ for (let attempt = 2; pathEntryExists(candidate); attempt += 1) {
564
+ candidate = `${base}-${attempt}`;
565
+ }
566
+ return candidate;
567
+ }
568
+ // A user may have edited their old copy, so a folder is moved to a backup
569
+ // folder that no agent scans, never deleted.
570
+ function moveToBackup(path, backupPath) {
571
+ mkdirSync(dirname(backupPath), { recursive: true });
572
+ try {
573
+ renameSync(path, backupPath);
574
+ }
575
+ catch (error) {
576
+ if (errorCode(error) !== 'EXDEV') {
577
+ throw error;
578
+ }
579
+ // A project on another disk cannot be renamed into the home folder.
580
+ cpSync(path, backupPath, { recursive: true, verbatimSymlinks: true });
581
+ rmSync(path, { recursive: true });
582
+ }
583
+ }
584
+ // Links are removed as links, never by following them. Windows refuses
585
+ // unlink on a directory link, and rmdir removes only the link there.
586
+ function removeLink(path) {
587
+ try {
588
+ unlinkSync(path);
589
+ }
590
+ catch (error) {
591
+ const code = errorCode(error);
592
+ if (code !== 'EPERM' && code !== 'EISDIR') {
593
+ throw error;
594
+ }
595
+ rmdirSync(path);
596
+ }
597
+ }
598
+ // Returns where a folder was moved to, or null for a link. A folder is checked
599
+ // again right before it is moved, in case it changed since it was found.
600
+ function removeLegacySkillInstall(install, options) {
601
+ const current = inspectLegacySkillPath(install.target, install.path);
602
+ if (!current || (current.kind === 'directory') !== (install.kind === 'directory')) {
603
+ throw new Error(`${install.path} changed while Orizu was replacing it; it was left in place.`);
604
+ }
605
+ if (current.kind !== 'directory') {
606
+ removeLink(install.path);
607
+ return null;
608
+ }
609
+ const backupPath = uniqueBackupPath(install, options);
610
+ moveToBackup(install.path, backupPath);
611
+ return backupPath;
612
+ }
613
+ function replaceOneLegacySkillInstall(install, options, heldBackReason) {
614
+ const installedPath = getSkillInstallPath(install.target, options);
615
+ const needsInstall = !pathEntryExists(installedPath);
616
+ const result = {
617
+ ...install,
618
+ installedPath: needsInstall ? installedPath : null,
619
+ backupPath: null,
620
+ reason: null,
621
+ error: null,
622
+ };
623
+ if (heldBackReason) {
624
+ return { ...result, action: 'left-in-place', installedPath: null, reason: heldBackReason };
625
+ }
626
+ if (options?.dryRun) {
627
+ return { ...result, action: needsInstall ? 'would-replace' : 'would-remove' };
628
+ }
629
+ let didInstall = false;
630
+ try {
631
+ if (needsInstall) {
632
+ installSkillTarget(install.target, {
633
+ ...options,
634
+ overwrite: false,
635
+ dryRun: false,
636
+ mode: options?.mode ?? (install.kind === 'directory' ? 'copy' : 'auto'),
637
+ });
638
+ didInstall = true;
639
+ }
640
+ const backupPath = removeLegacySkillInstall(install, options);
641
+ return { ...result, action: needsInstall ? 'replaced' : 'removed', backupPath };
642
+ }
643
+ catch (error) {
644
+ // Keep the new path when the install worked, so both facts are reported.
645
+ return {
646
+ ...result,
647
+ action: 'failed',
648
+ installedPath: didInstall ? installedPath : null,
649
+ error: errorMessage(error),
650
+ };
651
+ }
652
+ }
653
+ // Why an old install must be left alone, or null when it may be replaced.
654
+ function heldBackReasonFor(install, installs, options) {
655
+ const isSelected = (target) => !options?.targets || options.targets.includes(target);
656
+ const heldBack = options?.heldBack?.[install.target];
657
+ if (heldBack) {
658
+ return heldBack;
659
+ }
660
+ if (!isSelected(install.target)) {
661
+ return 'not a selected target';
662
+ }
663
+ if (install.kind !== 'directory') {
664
+ return null;
665
+ }
666
+ // Moving a folder would leave a link from an unselected target pointing
667
+ // nowhere, so the folder stays until that link can go too.
668
+ let folder;
669
+ try {
670
+ folder = realpathSync(install.path);
671
+ }
672
+ catch (error) {
673
+ return `it could not be checked: ${errorMessage(error)}`;
674
+ }
675
+ const blockingLink = installs.find(other => other.kind === 'link'
676
+ && other.resolvedPath === folder
677
+ && (!isSelected(other.target) || Boolean(options?.heldBack?.[other.target])));
678
+ return blockingLink ? `${blockingLink.path} links to it and is left in place` : null;
679
+ }
680
+ export function replaceLegacySkillInstalls(options) {
681
+ const { installs, problems } = findLegacySkillInstalls(options);
682
+ // Remove links before folders, so no link is left pointing at a moved folder.
683
+ const ordered = [
684
+ ...installs.filter(install => install.kind !== 'directory'),
685
+ ...installs.filter(install => install.kind === 'directory'),
686
+ ];
687
+ const reasons = new Map(ordered.map(install => [install, heldBackReasonFor(install, installs, options)]));
688
+ return {
689
+ replacements: ordered.map(install => replaceOneLegacySkillInstall(install, options, reasons.get(install) ?? null)),
690
+ problems,
691
+ };
692
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.31",
3
+ "version": "0.5.32",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  readFileSync,
5
+ statSync,
5
6
  mkdirSync,
6
7
  writeFileSync,
7
8
  realpathSync,
@@ -446,6 +447,14 @@ async function appDetail() {
446
447
  printLine(` Updated: ${sanitizeTerminalText(detail.updatedAt)}`)
447
448
  }
448
449
 
450
+ /** `--out` may be a file path or a directory; a directory gets the default file name. */
451
+ export function resolveAppExportPath(outPathArg: string | null | undefined, defaultFilename: string): string {
452
+ if (!outPathArg) return defaultFilename
453
+ const expanded = expandHomePath(outPathArg)
454
+ const isDirectory = /[\\/]$/.test(outPathArg) || statSync(expanded, { throwIfNoEntry: false })?.isDirectory() === true
455
+ return isDirectory ? join(expanded, defaultFilename) : expanded
456
+ }
457
+
449
458
  async function exportAppSource() {
450
459
  const project = getArg('--project')
451
460
  const versionArg = getArg('--version')
@@ -480,9 +489,10 @@ async function exportAppSource() {
480
489
  }
481
490
 
482
491
  const data = await parseJsonResponse<AppExportPayload>(response, 'App export')
483
- const filename = outPathArg
484
- ? expandHomePath(outPathArg)
485
- : defaultAppExportFilename(data.app.name, data.version.versionNum)
492
+ const filename = resolveAppExportPath(
493
+ outPathArg,
494
+ defaultAppExportFilename(data.app.name, data.version.versionNum)
495
+ )
486
496
 
487
497
  writeTextFileEnsuringDir(filename, data.version.code)
488
498
  if (hasJsonFlag()) {
package/src/help.ts CHANGED
@@ -307,13 +307,13 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
307
307
  options: [
308
308
  { name: '--team <slug>', help: 'Choose an existing accessible team.' },
309
309
  { name: '--create-team <name>', help: 'Create and select a new team; mutually exclusive with --team.' },
310
- { name: '--project <slug>', help: 'Choose the project to use as coding-agent handoff context. The workspace still materializes all team projects.' },
311
- { name: '--create-project <name>', help: 'Create and select a project in the chosen team; mutually exclusive with --project.' },
310
+ { name: '--project <slug>', help: 'Choose the project to work in. Only that project is set up in the workspace, and the team-project checklist is skipped, interactive or not.' },
311
+ { name: '--create-project <name>', help: 'Create and select a project in the chosen team, and set up only that project (no checklist); mutually exclusive with --project.' },
312
312
  { name: '--workspace [path]', help: 'Initialize or validate a workspace path, or the current directory when no path is provided. If provided, the value must be a filesystem path.' },
313
313
  { name: '--service-origin <url>', help: 'Service origin written to setup metadata. Defaults to the active CLI server.' },
314
314
  { name: '--attach-workspace <id>', help: 'Record an existing Orizu workspace id for future attachment.' },
315
315
  { name: '--validate', help: 'Inspect the workspace contract without writing files.' },
316
- { name: '--fix', help: 'Apply safe idempotent repairs only.' },
316
+ { name: '--fix', help: 'Apply safe idempotent repairs only, restoring every team project.' },
317
317
  { name: '--no-symlinks', help: 'Write CLAUDE.md as a pointer file instead of a symlink to AGENTS.md.' },
318
318
  { name: '--verbose', help: 'Show per-file setup actions. Default setup output is summary-oriented.' },
319
319
  { name: '--agent <agent>', help: 'Non-interactive opt-in to install the shared global Orizu skill plus any applicable native target. Repeat for multiple agents.', repeatable: true, choices: ['claude', 'codex', 'pi', 'devin', 'droid', 'grok', 'windsurf', 'opencode'] },
@@ -775,8 +775,8 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
775
775
  },
776
776
  {
777
777
  path: ['apps', 'export'],
778
- usage: 'orizu apps export [--app <appId>] [--project <team/project>] [--version <n>] [--out <path>]',
779
- summary: 'Export app source for the selected app version.',
778
+ usage: 'orizu apps export [--app <appId>] [--project <team/project>] [--version <n>] [--out <file-or-directory>]',
779
+ summary: 'Export app source for the selected app version. --out is a file path, or a directory (the file is named <app>.v<version>.tsx inside it).',
780
780
  group: 'Apps',
781
781
  },
782
782
  {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The system prompt for the hosted agent (ORI-2207).
3
+ *
4
+ * OpenCode (the program that runs the agent in a hosted session) ships its own
5
+ * built-in prompt that starts "You are opencode, an interactive CLI tool…", so
6
+ * the agent used to introduce itself as OpenCode. We register our own "primary
7
+ * agent" in the OpenCode config with this text, and every prompt request picks
8
+ * it by name. That replaces OpenCode's built-in prompt entirely, so this text
9
+ * carries the working rules we still want: use the tools, keep answers short.
10
+ * AGENTS.md is still added after it, as before.
11
+ */
12
+ export const HOSTED_AGENT_NAME = 'orizu'
13
+
14
+ export const HOSTED_AGENT_PROMPT = `You are the Orizu agent. You work in the person's repository inside an Orizu workbench, helping them with software engineering tasks. If asked who or what you are, say you are the Orizu agent. Never call yourself OpenCode.
15
+
16
+ - Do the work with your tools: read files, search, edit, and run commands. Do not guess at what a file contains when you can look.
17
+ - Keep answers short and plain. Say what you did and what the person needs to know next.
18
+ - Follow the repository's own conventions and any instructions in its AGENTS.md.`
@@ -51,6 +51,7 @@
51
51
 
52
52
  import { readFileSync } from 'fs'
53
53
 
54
+ import { HOSTED_AGENT_NAME, HOSTED_AGENT_PROMPT } from './hosted-agent-prompt.js'
54
55
  import { findProvider, parseModelIdentity } from './provider-registry.js'
55
56
 
56
57
  import { HARNESS_NAMES } from './hosted-harness.js'
@@ -193,6 +194,7 @@ export function buildPromptRequestBody(
193
194
  const body: Record<string, unknown> = {
194
195
  parts: [{ type: 'text', text: content }],
195
196
  messageID: opencodeMessageId,
197
+ agent: HOSTED_AGENT_NAME,
196
198
  }
197
199
  if (!model) return body
198
200
 
@@ -1133,6 +1135,8 @@ export function buildOpenCodeConfigContent(opts: OpenCodeConfigOptions): string
1133
1135
  return JSON.stringify({
1134
1136
  model: opts.model,
1135
1137
  permission: opts.permission ?? defaultHeadlessPermission(),
1138
+ // Replaces OpenCode's built-in "You are opencode…" prompt (ORI-2207).
1139
+ agent: { [HOSTED_AGENT_NAME]: { mode: 'primary', prompt: HOSTED_AGENT_PROMPT } },
1136
1140
  })
1137
1141
  }
1138
1142