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.
- package/dist/app-commands.d.ts +2 -0
- package/dist/app-commands.d.ts.map +1 -1
- package/dist/app-commands.js +10 -4
- package/dist/help.js +5 -5
- package/dist/hosted-agent-prompt.d.ts +14 -0
- package/dist/hosted-agent-prompt.d.ts.map +1 -0
- package/dist/hosted-agent-prompt.js +17 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +4 -0
- package/dist/hosted-optimization-prebaked-marker.d.ts +9 -0
- package/dist/hosted-optimization-prebaked-marker.d.ts.map +1 -0
- package/dist/hosted-optimization-prebaked-marker.js +22 -0
- package/dist/markdown-report-input.d.ts.map +1 -1
- package/dist/markdown-report-input.js +1 -1
- package/dist/preview-runtime.d.ts +7 -0
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +49 -5
- package/dist/provider-refusals.js +2 -2
- package/dist/report-markdown-blankness.d.ts +6 -0
- package/dist/report-markdown-blankness.d.ts.map +1 -1
- package/dist/report-markdown-blankness.js +47 -4
- package/dist/setup-and-skills-commands.d.ts.map +1 -1
- package/dist/setup-and-skills-commands.js +123 -9
- package/dist/skill-installer.d.ts +32 -0
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +228 -1
- package/package.json +1 -1
- package/src/app-commands.ts +13 -3
- package/src/help.ts +5 -5
- package/src/hosted-agent-prompt.ts +18 -0
- package/src/hosted-harness-opencode.ts +4 -0
- package/src/hosted-optimization-prebaked-marker.ts +20 -0
- package/src/markdown-report-input.ts +3 -1
- package/src/preview-runtime.ts +44 -5
- package/src/provider-refusals.ts +2 -2
- package/src/report-markdown-blankness.ts +59 -4
- package/src/setup-and-skills-commands.ts +160 -8
- package/src/skill-installer.ts +315 -0
- package/vendor/skills/orizu/SKILL.md +3 -3
- package/vendor/skills/orizu/flows/first-win.md +3 -1
- package/vendor/skills/orizu/flows/promote.md +2 -1
- package/vendor/skills/orizu/references/building-apps.md +2 -0
- package/vendor/skills/orizu/references/building-judges.md +138 -8
- package/vendor/skills/orizu/references/cli-reference.md +1 -1
- package/vendor/skills/orizu/references/optimization-with-gepa.md +21 -3
- package/vendor/skills/orizu/references/primer.md +2 -2
- 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
|
-
|
|
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: [
|
|
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":"
|
|
1
|
+
{"version":3,"file":"skill-installer.d.ts","sourceRoot":"","sources":["../src/skill-installer.ts"],"names":[],"mappings":"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"}
|
package/dist/skill-installer.js
CHANGED
|
@@ -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
package/src/app-commands.ts
CHANGED
|
@@ -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 =
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
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 <
|
|
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
|
|