orizu 0.5.49 → 0.5.50
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/account-directory.d.ts +2 -2
- package/dist/account-directory.d.ts.map +1 -1
- package/dist/account-directory.js +7 -32
- package/dist/auth-commands.d.ts.map +1 -1
- package/dist/auth-commands.js +22 -30
- package/dist/credentials.d.ts +5 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +84 -3
- package/dist/dataset-transfer-upload.d.ts.map +1 -1
- package/dist/dataset-transfer-upload.js +6 -1
- package/dist/directory-list-read.d.ts +37 -0
- package/dist/directory-list-read.d.ts.map +1 -0
- package/dist/directory-list-read.js +87 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +59 -41
- package/dist/hosted-harness-opencode.d.ts +31 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +45 -7
- package/dist/hosted-loop.d.ts +39 -2
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +127 -18
- package/dist/hosted-session-seed.d.ts +2 -0
- package/dist/hosted-session-seed.d.ts.map +1 -1
- package/dist/hosted-session-seed.js +27 -10
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +39 -234
- package/dist/keyboard-select.d.ts.map +1 -1
- package/dist/keyboard-select.js +23 -45
- package/dist/project-create-workflow.d.ts +18 -0
- package/dist/project-create-workflow.d.ts.map +1 -0
- package/dist/project-create-workflow.js +121 -0
- package/dist/self-update-command.d.ts +8 -0
- package/dist/self-update-command.d.ts.map +1 -0
- package/dist/self-update-command.js +390 -0
- package/dist/self-update.d.ts +29 -16
- package/dist/self-update.d.ts.map +1 -1
- package/dist/self-update.js +55 -244
- package/dist/setup-account.d.ts.map +1 -1
- package/dist/setup-account.js +4 -1
- package/dist/setup-and-skills-commands.d.ts.map +1 -1
- package/dist/setup-and-skills-commands.js +88 -102
- package/dist/skill-install-workflow.d.ts +23 -0
- package/dist/skill-install-workflow.d.ts.map +1 -0
- package/dist/skill-install-workflow.js +184 -0
- package/dist/skill-installer.d.ts +8 -3
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +26 -33
- package/dist/stored-session-requests.d.ts +27 -0
- package/dist/stored-session-requests.d.ts.map +1 -0
- package/dist/stored-session-requests.js +264 -0
- package/dist/terminal-key-session.d.ts +23 -0
- package/dist/terminal-key-session.d.ts.map +1 -0
- package/dist/terminal-key-session.js +74 -0
- package/package.json +1 -1
- package/src/account-directory.ts +8 -42
- package/src/auth-commands.ts +20 -29
- package/src/credentials.ts +83 -3
- package/src/dataset-transfer-upload.ts +7 -1
- package/src/directory-list-read.ts +123 -0
- package/src/hosted-boot.ts +65 -43
- package/src/hosted-harness-opencode.ts +65 -9
- package/src/hosted-loop.ts +152 -18
- package/src/hosted-session-seed.ts +25 -8
- package/src/http.ts +39 -317
- package/src/keyboard-select.ts +18 -55
- package/src/project-create-workflow.ts +139 -0
- package/src/self-update-command.ts +419 -0
- package/src/self-update.ts +70 -266
- package/src/setup-account.ts +4 -1
- package/src/setup-and-skills-commands.ts +89 -121
- package/src/skill-install-workflow.ts +215 -0
- package/src/skill-installer.ts +31 -38
- package/src/stored-session-requests.ts +316 -0
- package/src/terminal-key-session.ts +88 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// ORI-2044: moved from index.ts to keep setup and skills commands together.
|
|
2
2
|
import { createInterface } from 'readline/promises';
|
|
3
3
|
import { stdin as input, stdout as output } from 'process';
|
|
4
|
-
import { emitKeypressEvents } from 'readline';
|
|
5
4
|
import { join, relative, resolve } from 'path';
|
|
6
5
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
7
6
|
import { randomBytes } from 'crypto';
|
|
8
7
|
import { spawnSync } from 'child_process';
|
|
9
8
|
import { isInteractiveTerminal, getArg, getArgs, hasJsonFlag, hasArg, normalizeSlugInput, getOptionalArgValue, expandHomePath, cliArgs, } from './command-line-arguments.js';
|
|
10
9
|
import { printLine, getCliVersion, getErrorMessage, printJson, printBannerIfInteractive, } from './cli-console-output.js';
|
|
10
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
11
11
|
import { sanitizeTerminalText } from './json-response.js';
|
|
12
12
|
import { SKILL_INSTALL_AGENTS, SKILL_INSTALL_TARGETS, getTargetForAgent, getSkillInstallPath, isSkillInstallAgent, isSkillInstallTarget, installSkillTarget, targetNeedsOverwrite, resolveSkillSource, getSkillTargetStatus, computeSkillContentHash, findLegacySkillInstalls, replaceLegacySkillInstalls, PROJECT_SKILL_COPY_TARGETS, compareProjectSkillCopies, withProjectSkillCopyPair, } from './skill-installer.js';
|
|
13
13
|
import { createTeamOnServer, fetchTeams, fetchProjects, createProjectOnServer, } from './account-directory.js';
|
|
@@ -49,30 +49,13 @@ async function waitForAnyKeyOrEscape(message) {
|
|
|
49
49
|
throw new Error('Interactive terminal required.');
|
|
50
50
|
}
|
|
51
51
|
printLine(message);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
rawInput.setRawMode(false);
|
|
60
|
-
}
|
|
61
|
-
input.pause();
|
|
62
|
-
printLine('');
|
|
63
|
-
}
|
|
64
|
-
function onKeypress(_chunk, key) {
|
|
65
|
-
cleanup();
|
|
66
|
-
if (key.ctrl && key.name === 'c') {
|
|
67
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
resolvePromise(key.name !== 'escape');
|
|
71
|
-
}
|
|
72
|
-
input.on('keypress', onKeypress);
|
|
73
|
-
input.resume();
|
|
74
|
-
rawInput.setRawMode?.(true);
|
|
75
|
-
});
|
|
52
|
+
const { readKeys } = await import('./terminal-key-session.js');
|
|
53
|
+
try {
|
|
54
|
+
return await readKeys(key => ({ kind: 'done', value: key.name !== 'escape' }), { hideCursor: false });
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
printLine('');
|
|
58
|
+
}
|
|
76
59
|
}
|
|
77
60
|
async function promptKeyboardMultiSelect(title, items, label, defaultSelected, options = {}) {
|
|
78
61
|
if (items.length === 0) {
|
|
@@ -91,9 +74,6 @@ async function promptKeyboardMultiSelect(title, items, label, defaultSelected, o
|
|
|
91
74
|
checked.add(index);
|
|
92
75
|
}
|
|
93
76
|
});
|
|
94
|
-
const rawInput = input;
|
|
95
|
-
const wasRaw = Boolean(rawInput.isRaw);
|
|
96
|
-
emitKeypressEvents(input);
|
|
97
77
|
function render() {
|
|
98
78
|
if (rendered) {
|
|
99
79
|
output.write(`\x1b[${items.length}A`);
|
|
@@ -107,58 +87,34 @@ async function promptKeyboardMultiSelect(title, items, label, defaultSelected, o
|
|
|
107
87
|
rendered = true;
|
|
108
88
|
}
|
|
109
89
|
render();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
input.off('keypress', onKeypress);
|
|
114
|
-
if (rawInput.setRawMode && !wasRaw) {
|
|
115
|
-
rawInput.setRawMode(false);
|
|
116
|
-
}
|
|
117
|
-
input.pause();
|
|
118
|
-
output.write('\x1b[?25h');
|
|
119
|
-
printLine('');
|
|
120
|
-
}
|
|
121
|
-
function onKeypress(_chunk, key) {
|
|
122
|
-
if (key.ctrl && key.name === 'c') {
|
|
123
|
-
cleanup();
|
|
124
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
90
|
+
const { readKeys } = await import('./terminal-key-session.js');
|
|
91
|
+
try {
|
|
92
|
+
return await readKeys(key => {
|
|
127
93
|
if (key.name === 'escape') {
|
|
128
|
-
|
|
129
|
-
resolvePromise(options.escapeKeepsDefaults ? items.filter((item, index) => defaultSelected(item, index)) : []);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (key.name === 'up') {
|
|
133
|
-
selected = (selected - 1 + items.length) % items.length;
|
|
134
|
-
render();
|
|
135
|
-
return;
|
|
94
|
+
return { kind: 'done', value: options.escapeKeepsDefaults ? items.filter((item, index) => defaultSelected(item, index)) : [] };
|
|
136
95
|
}
|
|
137
|
-
if (key.name === 'down') {
|
|
138
|
-
selected = (selected + 1) % items.length;
|
|
96
|
+
if (key.name === 'up' || key.name === 'down') {
|
|
97
|
+
selected = (selected + (key.name === 'up' ? -1 : 1) + items.length) % items.length;
|
|
139
98
|
render();
|
|
140
|
-
return;
|
|
99
|
+
return { kind: 'wait' };
|
|
141
100
|
}
|
|
142
101
|
if (key.name === 'space') {
|
|
143
|
-
if (checked.has(selected))
|
|
102
|
+
if (checked.has(selected))
|
|
144
103
|
checked.delete(selected);
|
|
145
|
-
|
|
146
|
-
else {
|
|
104
|
+
else
|
|
147
105
|
checked.add(selected);
|
|
148
|
-
}
|
|
149
106
|
render();
|
|
150
|
-
return;
|
|
107
|
+
return { kind: 'wait' };
|
|
151
108
|
}
|
|
152
109
|
if (key.name === 'return' || key.name === 'enter') {
|
|
153
|
-
|
|
154
|
-
cleanup();
|
|
155
|
-
resolvePromise(selectedItems);
|
|
110
|
+
return { kind: 'done', value: items.filter((_item, index) => checked.has(index)) };
|
|
156
111
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
112
|
+
return { kind: 'wait' };
|
|
113
|
+
}, { hideCursor: true });
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
printLine('');
|
|
117
|
+
}
|
|
162
118
|
}
|
|
163
119
|
function formatFindingCount(count, label) {
|
|
164
120
|
return `${count} ${label}${count === 1 ? '' : 's'}`;
|
|
@@ -332,13 +288,13 @@ function describePlannedWrite(target, action, mode, path) {
|
|
|
332
288
|
async function applySkillInstallTargets(targets, options) {
|
|
333
289
|
const cliVersion = getCliVersion();
|
|
334
290
|
const jsonMode = hasJsonFlag();
|
|
335
|
-
const planned = targets.map(target => installSkillTarget(target, {
|
|
291
|
+
const planned = await Promise.all(targets.map(target => installSkillTarget(target, {
|
|
336
292
|
cwd: options.cwd,
|
|
337
293
|
homeDir: options.homeDir,
|
|
338
294
|
dryRun: true,
|
|
339
295
|
mode: options.mode,
|
|
340
296
|
cliVersion,
|
|
341
|
-
}));
|
|
297
|
+
})));
|
|
342
298
|
const showPlan = options.showPlan ?? true;
|
|
343
299
|
const printResults = options.printResults ?? true;
|
|
344
300
|
if (showPlan && !jsonMode) {
|
|
@@ -377,7 +333,7 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
377
333
|
}
|
|
378
334
|
}
|
|
379
335
|
try {
|
|
380
|
-
const result = installSkillTarget(target, {
|
|
336
|
+
const result = await installSkillTarget(target, {
|
|
381
337
|
cwd: options.cwd,
|
|
382
338
|
homeDir: options.homeDir,
|
|
383
339
|
overwrite,
|
|
@@ -386,13 +342,17 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
386
342
|
});
|
|
387
343
|
if (printResults && !jsonMode) {
|
|
388
344
|
const suffix = result.mode === 'link' ? ' (symlink)' : '';
|
|
389
|
-
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}`);
|
|
345
|
+
printLine(`${formatSkillInstallAction(result.action)} ${result.path}${suffix}${describeBackup(result.backup)}`);
|
|
346
|
+
if (result.warning)
|
|
347
|
+
printLine(sanitizeSetupHumanInlineText(`Warning: ${result.warning}`));
|
|
390
348
|
}
|
|
391
349
|
outcomes.push({
|
|
392
350
|
target,
|
|
393
351
|
path: result.path,
|
|
394
352
|
action: result.action,
|
|
395
353
|
mode: result.mode,
|
|
354
|
+
...(result.backup ? { backup: result.backup } : {}),
|
|
355
|
+
...(result.warning ? { warning: result.warning } : {}),
|
|
396
356
|
});
|
|
397
357
|
}
|
|
398
358
|
catch (error) {
|
|
@@ -405,6 +365,9 @@ async function applySkillInstallTargets(targets, options) {
|
|
|
405
365
|
}
|
|
406
366
|
return outcomes;
|
|
407
367
|
}
|
|
368
|
+
function describeBackup(backup) {
|
|
369
|
+
return backup ? `; moved your earlier folder to ${backup}` : '';
|
|
370
|
+
}
|
|
408
371
|
async function installSkillCommand() {
|
|
409
372
|
const skipConfirm = hasArg('--yes');
|
|
410
373
|
const dryRun = hasArg('--dry-run');
|
|
@@ -424,14 +387,14 @@ async function installSkillCommand() {
|
|
|
424
387
|
return;
|
|
425
388
|
}
|
|
426
389
|
const outcomes = await applySkillInstallTargets(targets, { mode, skipConfirm, dryRun });
|
|
427
|
-
|
|
428
|
-
if (failedCopy) {
|
|
390
|
+
if (outcomes.some(outcome => outcome.action === 'failed')) {
|
|
429
391
|
process.exitCode = 1;
|
|
430
|
-
if (!hasJsonFlag()) {
|
|
431
|
-
printLine('The two project copies were not both written. Fix the error above and run the command again.');
|
|
432
|
-
}
|
|
433
392
|
}
|
|
434
|
-
const
|
|
393
|
+
const failedCopy = outcomes.some(outcome => outcome.action === 'failed' && PROJECT_SKILL_COPY_TARGETS.includes(outcome.target));
|
|
394
|
+
if (failedCopy && !hasJsonFlag()) {
|
|
395
|
+
printLine('The two project copies were not both written. Fix the error above and run the command again.');
|
|
396
|
+
}
|
|
397
|
+
const legacy = await replaceLegacySkillInstalls({
|
|
435
398
|
...legacyScopeAfterInstall(targets, outcomes, dryRun),
|
|
436
399
|
mode,
|
|
437
400
|
dryRun,
|
|
@@ -579,7 +542,18 @@ async function skillsUpdateCommand() {
|
|
|
579
542
|
const dryRun = hasArg('--dry-run');
|
|
580
543
|
const cliVersion = getCliVersion();
|
|
581
544
|
const updates = [];
|
|
582
|
-
|
|
545
|
+
// A failed target is reported with its reason; the old install there stays whole.
|
|
546
|
+
const refresh = async (target, path, options, action) => {
|
|
547
|
+
try {
|
|
548
|
+
const result = await installSkillTarget(target, options);
|
|
549
|
+
updates.push({ target, path, action: action(result), ...(result.backup ? { backup: result.backup } : {}), ...(result.warning ? { warning: result.warning } : {}) });
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
process.exitCode = 1;
|
|
553
|
+
updates.push({ target, path, action: 'failed', error: getErrorMessage(error) });
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const legacy = await replaceLegacySkillInstalls({ dryRun, cliVersion });
|
|
583
557
|
// Two project copies that differ are both rewritten from the installed CLI
|
|
584
558
|
// skill, including a hand-made copy the loop below would leave alone.
|
|
585
559
|
const projectCopies = compareProjectSkillCopies();
|
|
@@ -591,8 +565,7 @@ async function skillsUpdateCommand() {
|
|
|
591
565
|
updates.push({ target, path, action: 'would-rewrite' });
|
|
592
566
|
continue;
|
|
593
567
|
}
|
|
594
|
-
|
|
595
|
-
updates.push({ target, path, action: 'rewrote' });
|
|
568
|
+
await refresh(target, path, { overwrite: true, mode: 'copy', cliVersion }, () => 'rewrote');
|
|
596
569
|
}
|
|
597
570
|
}
|
|
598
571
|
for (const target of SKILL_INSTALL_TARGETS) {
|
|
@@ -612,16 +585,7 @@ async function skillsUpdateCommand() {
|
|
|
612
585
|
continue;
|
|
613
586
|
}
|
|
614
587
|
const refreshMode = status.mode === 'link' ? 'auto' : 'copy';
|
|
615
|
-
|
|
616
|
-
overwrite: true,
|
|
617
|
-
mode: refreshMode,
|
|
618
|
-
cliVersion,
|
|
619
|
-
});
|
|
620
|
-
updates.push({
|
|
621
|
-
target,
|
|
622
|
-
path: status.path,
|
|
623
|
-
action: status.state === 'broken-link' ? 'relinked' : result.action,
|
|
624
|
-
});
|
|
588
|
+
await refresh(target, status.path, { overwrite: true, mode: refreshMode, cliVersion }, result => status.state === 'broken-link' ? 'relinked' : result.action);
|
|
625
589
|
}
|
|
626
590
|
if (hasJsonFlag()) {
|
|
627
591
|
printJson({ updates, legacy: legacy.replacements, legacyProblems: legacy.problems });
|
|
@@ -633,14 +597,30 @@ async function skillsUpdateCommand() {
|
|
|
633
597
|
return;
|
|
634
598
|
}
|
|
635
599
|
if (rewriteProjectCopies) {
|
|
636
|
-
const
|
|
637
|
-
|
|
600
|
+
const rewrote = updates.filter(update => update.action === 'rewrote');
|
|
601
|
+
if (dryRun) {
|
|
602
|
+
printLine(`Would rewrite both project copies from the installed CLI skill: ${projectCopies.paths.join(' and ')}`);
|
|
603
|
+
}
|
|
604
|
+
else if (rewrote.length === PROJECT_SKILL_COPY_TARGETS.length) {
|
|
605
|
+
printLine(`Rewrote both project copies from the installed CLI skill: ${projectCopies.paths.join(' and ')}`);
|
|
606
|
+
}
|
|
638
607
|
}
|
|
639
608
|
for (const update of updates) {
|
|
640
|
-
if (update.action === '
|
|
609
|
+
if (update.action === 'failed') {
|
|
610
|
+
printLine(sanitizeSetupHumanInlineText(`Failed ${update.path}: ${update.error}`));
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (update.action === 'would-rewrite') {
|
|
641
614
|
continue;
|
|
642
615
|
}
|
|
643
|
-
if (update.action === '
|
|
616
|
+
if (update.action === 'rewrote') {
|
|
617
|
+
// With both rewritten, the line above names them; otherwise each says so here.
|
|
618
|
+
const bothRewritten = updates.filter(other => other.action === 'rewrote').length === PROJECT_SKILL_COPY_TARGETS.length;
|
|
619
|
+
if (!bothRewritten || update.backup) {
|
|
620
|
+
printLine(sanitizeSetupHumanInlineText(`Rewrote ${update.path} from the installed CLI skill${describeBackup(update.backup)}`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
else if (update.action === 'already-current') {
|
|
644
624
|
printLine(`Current ${update.path}`);
|
|
645
625
|
}
|
|
646
626
|
else if (update.action === 'would-update') {
|
|
@@ -650,8 +630,10 @@ async function skillsUpdateCommand() {
|
|
|
650
630
|
printLine(`Relinked ${update.path}`);
|
|
651
631
|
}
|
|
652
632
|
else {
|
|
653
|
-
printLine(`Updated ${update.path}`);
|
|
633
|
+
printLine(sanitizeSetupHumanInlineText(`Updated ${update.path}${describeBackup(update.backup)}`));
|
|
654
634
|
}
|
|
635
|
+
if (update.warning)
|
|
636
|
+
printLine(sanitizeSetupHumanInlineText(`Warning: ${update.warning}`));
|
|
655
637
|
}
|
|
656
638
|
}
|
|
657
639
|
function skillsPathCommand() {
|
|
@@ -684,7 +666,7 @@ async function resolveSetupTeam(teamSlugArg, createTeamName, noInput, dryRun, wo
|
|
|
684
666
|
throw new Error('--create-team requires a non-empty team name.');
|
|
685
667
|
return await createSetupTeam(name, request);
|
|
686
668
|
}
|
|
687
|
-
const teams = await fetchTeams(request);
|
|
669
|
+
const teams = await fetchTeams(request, datasetTransferTimeoutMs());
|
|
688
670
|
const normalizedTeamSlug = teamSlugArg ? normalizeSlugInput(teamSlugArg) : null;
|
|
689
671
|
if (normalizedTeamSlug) {
|
|
690
672
|
const team = teams.find(candidate => candidate.slug === normalizedTeamSlug);
|
|
@@ -731,7 +713,7 @@ async function resolveSetupProjects(team, projectSlugArg, createProjectName, noI
|
|
|
731
713
|
if (createProjectName && !canCreateProject) {
|
|
732
714
|
throw new Error(`Only team admins can create projects in '${team.slug}'. Choose an existing project or ask a team admin to create one.`);
|
|
733
715
|
}
|
|
734
|
-
const projects = await fetchProjects(team.slug, request);
|
|
716
|
+
const projects = await fetchProjects(team.slug, request, datasetTransferTimeoutMs());
|
|
735
717
|
if (createProjectName) {
|
|
736
718
|
if (dryRun) {
|
|
737
719
|
throw new Error('--create-project cannot be used with --dry-run because setup cannot preview a server-generated project slug.');
|
|
@@ -926,7 +908,7 @@ async function setupCommand() {
|
|
|
926
908
|
throw new Error(`This directory is already an Orizu workspace for team '${workspaceTeamSlug}'. Run setup in another directory to set up team '${setupTeam.slug}'.`);
|
|
927
909
|
}
|
|
928
910
|
if (fix) {
|
|
929
|
-
setupProjects = await fetchProjects(setupTeam.slug, setupRequest);
|
|
911
|
+
setupProjects = await fetchProjects(setupTeam.slug, setupRequest, datasetTransferTimeoutMs());
|
|
930
912
|
if (setupProjects.length === 0)
|
|
931
913
|
throw new Error(`Team '${setupTeam.slug}' has no projects to restore.`);
|
|
932
914
|
}
|
|
@@ -1052,7 +1034,7 @@ async function setupCommand() {
|
|
|
1052
1034
|
if (dryRun) {
|
|
1053
1035
|
printLine('Dry run: no skill files were changed.');
|
|
1054
1036
|
}
|
|
1055
|
-
const legacy = replaceLegacySkillInstalls({
|
|
1037
|
+
const legacy = await replaceLegacySkillInstalls({
|
|
1056
1038
|
...legacyScopeAfterInstall(targets, installOutcomes, dryRun),
|
|
1057
1039
|
mode: installMode,
|
|
1058
1040
|
dryRun,
|
|
@@ -1070,7 +1052,9 @@ async function setupCommand() {
|
|
|
1070
1052
|
printLine(`${label} skipped`);
|
|
1071
1053
|
}
|
|
1072
1054
|
else {
|
|
1073
|
-
printLine(`${label} installed`);
|
|
1055
|
+
printLine(sanitizeSetupHumanInlineText(`${label} installed${describeBackup(outcome.backup)}`));
|
|
1056
|
+
if (outcome.warning)
|
|
1057
|
+
printLine(sanitizeSetupHumanInlineText(`Warning: ${outcome.warning}`));
|
|
1074
1058
|
}
|
|
1075
1059
|
}
|
|
1076
1060
|
}
|
|
@@ -1165,6 +1149,8 @@ async function setupCommand() {
|
|
|
1165
1149
|
action: outcome.action,
|
|
1166
1150
|
mode: outcome.mode,
|
|
1167
1151
|
error: outcome.error,
|
|
1152
|
+
...(outcome.backup ? { backup: outcome.backup } : {}),
|
|
1153
|
+
...(outcome.warning ? { warning: outcome.warning } : {}),
|
|
1168
1154
|
})),
|
|
1169
1155
|
skill: {
|
|
1170
1156
|
root: source.root,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ReplaceSkillFolderInput {
|
|
2
|
+
sourceRoot: string;
|
|
3
|
+
targetPath: string;
|
|
4
|
+
mode: 'link' | 'copy';
|
|
5
|
+
/** Where a person's own folder is moved; no agent looks for skills there. */
|
|
6
|
+
backupPath: string;
|
|
7
|
+
/** Writes Orizu's marker into the new copy before it is put in place. */
|
|
8
|
+
writeMarker: (dir: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ReplaceSkillFolderResult {
|
|
11
|
+
/** Where a person's own folder was moved, when there was one. */
|
|
12
|
+
backup: string | null;
|
|
13
|
+
/** Why a person's folder, or part of it, is still beside the skill. */
|
|
14
|
+
warning?: string;
|
|
15
|
+
}
|
|
16
|
+
/** One replacement whose renames go through `move`, so a test can make one fail. */
|
|
17
|
+
export declare function replaceSkillFolderUsing(move: (from: string, to: string) => Promise<void>, input: ReplaceSkillFolderInput): Promise<ReplaceSkillFolderResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Runs one replacement. Ctrl-C or SIGTERM interrupts it; once staging is
|
|
20
|
+
* removed and the old folder is back, the process ends by that same signal.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runSkillFolderReplacement(input: ReplaceSkillFolderInput): Promise<ReplaceSkillFolderResult>;
|
|
23
|
+
//# sourceMappingURL=skill-install-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-install-workflow.d.ts","sourceRoot":"","sources":["../src/skill-install-workflow.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAuJD,oFAAoF;AACpF,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAElK;AAaD;;;GAGG;AACH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAgBjH"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Reads the skill source and the install target it is given.
|
|
2
|
+
// @tia-reads: skills/orizu
|
|
3
|
+
// Replaces one skill folder without ever leaving a half-written one, and never
|
|
4
|
+
// deletes a person's files: they are moved to a backup first. Loaded only
|
|
5
|
+
// through `await import(...)`, so `orizu skills path` never loads Effect.
|
|
6
|
+
import { cp, lstat, mkdir, readdir, readlink, readFile, rename, rm, symlink, unlink, copyFile } from 'node:fs/promises';
|
|
7
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { Context, Effect, Exit, Option, Schema } from 'effect';
|
|
9
|
+
import { computeSkillContentHash, SKILL_META_FILENAME, shouldCopySkillPath } from './skill-installer.js';
|
|
10
|
+
/** Every rename the replacement makes. Tests swap it to make one rename fail. */
|
|
11
|
+
const SkillFolderRename = Context.Reference('orizu/SkillFolderRename', {
|
|
12
|
+
defaultValue: () => rename,
|
|
13
|
+
});
|
|
14
|
+
const renameEntry = (from, to) => Effect.gen(function* () {
|
|
15
|
+
const move = yield* SkillFolderRename;
|
|
16
|
+
yield* attempt(() => move(from, to));
|
|
17
|
+
});
|
|
18
|
+
const SkillMarker = Schema.Struct({ skillHash: Schema.String });
|
|
19
|
+
const attempt = (run) =>
|
|
20
|
+
// One file step at a time: an interruption lands between steps, never during one.
|
|
21
|
+
Effect.uninterruptible(Effect.tryPromise({ try: run, catch: cause => cause instanceof Error ? cause : new Error(String(cause)) }));
|
|
22
|
+
const exists = (path) => attempt(() => lstat(path).then(() => true, () => false));
|
|
23
|
+
// Orizu's own install is a link, or a copy whose files still match its marker
|
|
24
|
+
// or the skill being installed. Anything else may hold a person's work.
|
|
25
|
+
function classify(targetPath, sourceRoot) {
|
|
26
|
+
return Effect.gen(function* () {
|
|
27
|
+
const stats = yield* attempt(() => lstat(targetPath).then(found => found, () => null));
|
|
28
|
+
if (!stats)
|
|
29
|
+
return 'missing';
|
|
30
|
+
if (stats.isSymbolicLink())
|
|
31
|
+
return 'link';
|
|
32
|
+
if (!stats.isDirectory())
|
|
33
|
+
return 'person';
|
|
34
|
+
const marker = yield* attempt(() => readFile(join(targetPath, SKILL_META_FILENAME), 'utf8').then(text => JSON.parse(text), () => null));
|
|
35
|
+
const installedHash = computeSkillContentHash(targetPath);
|
|
36
|
+
const recorded = Schema.decodeUnknownOption(SkillMarker)(marker);
|
|
37
|
+
if (Option.isSome(recorded) && recorded.value.skillHash === installedHash)
|
|
38
|
+
return 'orizu';
|
|
39
|
+
return installedHash === computeSkillContentHash(sourceRoot) ? 'orizu' : 'person';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function copyTree(root, from, to) {
|
|
43
|
+
return Effect.gen(function* () {
|
|
44
|
+
yield* attempt(() => mkdir(to, { recursive: true }));
|
|
45
|
+
const entries = yield* attempt(() => readdir(from, { withFileTypes: true }));
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const source = join(from, entry.name);
|
|
48
|
+
const target = join(to, entry.name);
|
|
49
|
+
if (!shouldCopySkillPath(root, source))
|
|
50
|
+
continue;
|
|
51
|
+
if (entry.isDirectory())
|
|
52
|
+
yield* copyTree(root, source, target);
|
|
53
|
+
else if (entry.isFile())
|
|
54
|
+
yield* attempt(() => copyFile(source, target));
|
|
55
|
+
else if (entry.isSymbolicLink())
|
|
56
|
+
yield* attempt(async () => symlink(await readlink(source), target));
|
|
57
|
+
else
|
|
58
|
+
yield* Effect.fail(new Error(`${source} is not a file or folder, so the skill can't be copied.`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Links are removed as links, never by following them.
|
|
63
|
+
const removeEntry = (path) => attempt(async () => {
|
|
64
|
+
const stats = await lstat(path).catch(() => null);
|
|
65
|
+
if (!stats)
|
|
66
|
+
return;
|
|
67
|
+
if (stats.isSymbolicLink())
|
|
68
|
+
await unlink(path);
|
|
69
|
+
else
|
|
70
|
+
await rm(path, { recursive: true, force: true });
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Moves a person's folder to the backup. Once the copy is whole, the backup
|
|
74
|
+
* is reported even when the original cannot be removed; if nothing could be
|
|
75
|
+
* moved, the folder stays where it is and the warning says why.
|
|
76
|
+
*/
|
|
77
|
+
function moveToBackup(from, to) {
|
|
78
|
+
const stayed = (cause) => ({
|
|
79
|
+
backup: resolve(from),
|
|
80
|
+
warning: `Could not move your earlier folder to ${to}: ${cause.message}. It is still at ${from}, where agents may load it as a second skill; move it somewhere else.`,
|
|
81
|
+
});
|
|
82
|
+
return Effect.gen(function* () {
|
|
83
|
+
yield* attempt(() => mkdir(dirname(to), { recursive: true }));
|
|
84
|
+
// A project on another disk cannot be renamed into the home folder, so it is copied.
|
|
85
|
+
const renamed = yield* renameEntry(from, to).pipe(Effect.as(true), Effect.catch(error => error.code === 'EXDEV' ? Effect.succeed(false) : Effect.fail(error)));
|
|
86
|
+
if (renamed)
|
|
87
|
+
return { backup: resolve(to) };
|
|
88
|
+
// Our own half-made copy is removed; the person's folder is untouched.
|
|
89
|
+
yield* attempt(() => cp(from, to, { recursive: true, verbatimSymlinks: true })).pipe(Effect.tapError(() => removeEntry(to).pipe(Effect.ignore)));
|
|
90
|
+
return yield* attempt(() => rm(from, { recursive: true })).pipe(Effect.as({ backup: resolve(to) }), Effect.catch(cause => Effect.succeed({
|
|
91
|
+
backup: resolve(to),
|
|
92
|
+
warning: `Copied your earlier folder to ${to}, but could not remove it from ${from}: ${cause.message}. Agents may load it there as a second skill; remove it once you have checked the copy.`,
|
|
93
|
+
})));
|
|
94
|
+
}).pipe(Effect.catch(cause => Effect.succeed(stayed(cause))));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Stage the new skill beside the target, move the old folder aside, rename the
|
|
98
|
+
* staged one into place, then drop Orizu's old copy or back up a person's.
|
|
99
|
+
* On a failure or an interruption, staging is removed and the old folder is
|
|
100
|
+
* put back.
|
|
101
|
+
*/
|
|
102
|
+
function replaceSkillFolder(input) {
|
|
103
|
+
const { sourceRoot, targetPath, mode } = input;
|
|
104
|
+
const id = `${process.pid}-${Date.now().toString(36)}`;
|
|
105
|
+
const name = basename(targetPath);
|
|
106
|
+
const staging = join(dirname(targetPath), `${name}.orizu-staging-${id}`);
|
|
107
|
+
const aside = join(dirname(targetPath), `${name}.orizu-previous-${id}`);
|
|
108
|
+
// Set when the old folder could not be put back after a failure.
|
|
109
|
+
let strandedAt = null;
|
|
110
|
+
// Only the copy into staging can be interrupted. Once the old folder moves
|
|
111
|
+
// aside, the swap and the clean-up after it always finish, so Ctrl-C can
|
|
112
|
+
// never skip putting the old folder back or moving a person's folder out.
|
|
113
|
+
return Effect.uninterruptibleMask(restore => Effect.gen(function* () {
|
|
114
|
+
const existing = yield* classify(targetPath, sourceRoot);
|
|
115
|
+
yield* attempt(() => mkdir(dirname(targetPath), { recursive: true }));
|
|
116
|
+
const swapped = yield* Effect.scoped(Effect.gen(function* () {
|
|
117
|
+
// Registered before staging starts, so a copy that stops part way is removed too.
|
|
118
|
+
// Gone already once it has been renamed into place.
|
|
119
|
+
yield* Effect.addFinalizer(() => removeEntry(staging).pipe(Effect.ignore));
|
|
120
|
+
yield* restore(mode === 'link'
|
|
121
|
+
? attempt(() => symlink(sourceRoot, staging, 'dir'))
|
|
122
|
+
: copyTree(sourceRoot, sourceRoot, staging).pipe(Effect.andThen(Effect.try({ try: () => input.writeMarker(staging), catch: cause => cause instanceof Error ? cause : new Error(String(cause)) }))));
|
|
123
|
+
if (existing !== 'missing') {
|
|
124
|
+
yield* Effect.acquireRelease(renameEntry(targetPath, aside), (_, exit) => Exit.isSuccess(exit)
|
|
125
|
+
? Effect.void
|
|
126
|
+
// Put the old folder back unless something already holds its place.
|
|
127
|
+
: exists(targetPath).pipe(Effect.flatMap(taken => taken ? Effect.fail(new Error(`${targetPath} is taken`)) : renameEntry(aside, targetPath)), Effect.catch(() => Effect.sync(() => { strandedAt = aside; }))));
|
|
128
|
+
}
|
|
129
|
+
yield* renameEntry(staging, targetPath);
|
|
130
|
+
return existing;
|
|
131
|
+
})).pipe(Effect.mapError(error => strandedAt
|
|
132
|
+
? new Error(`${error.message} Your earlier folder could not be put back at ${targetPath}; it is at ${strandedAt}.`)
|
|
133
|
+
: error));
|
|
134
|
+
if (swapped === 'missing')
|
|
135
|
+
return { backup: null };
|
|
136
|
+
if (swapped !== 'person') {
|
|
137
|
+
yield* removeEntry(aside).pipe(Effect.mapError(cause => new Error(`Installed ${targetPath}, but could not remove the earlier copy at ${aside}: ${cause.message}`)));
|
|
138
|
+
return { backup: null };
|
|
139
|
+
}
|
|
140
|
+
// The person's folder is safe beside the target; move it out of agents' sight.
|
|
141
|
+
return yield* moveToBackup(aside, input.backupPath);
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/** One replacement whose renames go through `move`, so a test can make one fail. */
|
|
145
|
+
export async function replaceSkillFolderUsing(move, input) {
|
|
146
|
+
return await Effect.runPromise(replaceSkillFolder(input).pipe(Effect.provideService(SkillFolderRename, move)));
|
|
147
|
+
}
|
|
148
|
+
const STOP_SIGNALS = ['SIGINT', 'SIGTERM'];
|
|
149
|
+
/** Ends the process by the signal that stopped it, once its handlers are gone. */
|
|
150
|
+
async function endBy(signal, handlers) {
|
|
151
|
+
for (const [name, handler] of handlers)
|
|
152
|
+
process.removeListener(name, handler);
|
|
153
|
+
process.kill(process.pid, signal);
|
|
154
|
+
// The signal ends the process; if another listener holds it, exit as a shell would.
|
|
155
|
+
setTimeout(() => process.exit(signal === 'SIGINT' ? 130 : 143), 1_000);
|
|
156
|
+
return await new Promise(() => { });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Runs one replacement. Ctrl-C or SIGTERM interrupts it; once staging is
|
|
160
|
+
* removed and the old folder is back, the process ends by that same signal.
|
|
161
|
+
*/
|
|
162
|
+
export async function runSkillFolderReplacement(input) {
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const stop = { by: null };
|
|
165
|
+
const handlers = STOP_SIGNALS.map(signal => [signal, () => { stop.by = signal; controller.abort(); }]);
|
|
166
|
+
for (const [signal, handler] of handlers)
|
|
167
|
+
process.once(signal, handler);
|
|
168
|
+
try {
|
|
169
|
+
const result = await Effect.runPromise(replaceSkillFolder(input), { signal: controller.signal });
|
|
170
|
+
// A stop that arrived as the replacement finished still stops the command.
|
|
171
|
+
if (stop.by)
|
|
172
|
+
return await endBy(stop.by, handlers);
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (!stop.by)
|
|
177
|
+
throw error;
|
|
178
|
+
return await endBy(stop.by, handlers);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
for (const [signal, handler] of handlers)
|
|
182
|
+
process.removeListener(signal, handler);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -21,6 +21,10 @@ export interface SkillInstallResult {
|
|
|
21
21
|
path: string;
|
|
22
22
|
action: 'created' | 'updated' | 'would-create' | 'would-update';
|
|
23
23
|
mode: 'link' | 'copy' | 'section';
|
|
24
|
+
/** Where a person's own folder at the path was moved before replacing it. */
|
|
25
|
+
backup?: string;
|
|
26
|
+
/** Why a person's folder, or part of it, is still beside the skill. */
|
|
27
|
+
warning?: string;
|
|
24
28
|
}
|
|
25
29
|
export interface SkillInstallMeta {
|
|
26
30
|
name: string;
|
|
@@ -65,9 +69,10 @@ export declare function compareProjectSkillCopies(options?: SkillInstallOptions)
|
|
|
65
69
|
export declare function isProjectLevelTarget(target: SkillInstallTarget): boolean;
|
|
66
70
|
export declare function isStableSkillSourcePath(root: string): boolean;
|
|
67
71
|
export declare function resolveEffectiveInstallMode(target: SkillInstallTarget, sourceRoot: string, mode?: SkillInstallMode): 'link' | 'copy' | 'section';
|
|
72
|
+
export declare function shouldCopySkillPath(sourceRoot: string, sourcePath: string): boolean;
|
|
68
73
|
export declare function targetNeedsOverwrite(target: SkillInstallTarget, options?: SkillInstallOptions): boolean;
|
|
69
74
|
export declare function readSkillMeta(targetDir: string): SkillInstallMeta | null;
|
|
70
|
-
export declare function installSkillTarget(target: SkillInstallTarget, options?: SkillInstallOptions): SkillInstallResult
|
|
75
|
+
export declare function installSkillTarget(target: SkillInstallTarget, options?: SkillInstallOptions): Promise<SkillInstallResult>;
|
|
71
76
|
export type SkillTargetState = 'current' | 'stale' | 'missing' | 'broken-link' | 'unmanaged';
|
|
72
77
|
export interface SkillTargetStatus {
|
|
73
78
|
target: SkillInstallTarget;
|
|
@@ -109,8 +114,8 @@ export interface LegacySkillReplaceOptions extends SkillInstallOptions {
|
|
|
109
114
|
heldBack?: Partial<Record<SkillInstallTarget, string>>;
|
|
110
115
|
}
|
|
111
116
|
export declare function findLegacySkillInstalls(options?: SkillInstallOptions): LegacySkillScan;
|
|
112
|
-
export declare function replaceLegacySkillInstalls(options?: LegacySkillReplaceOptions): {
|
|
117
|
+
export declare function replaceLegacySkillInstalls(options?: LegacySkillReplaceOptions): Promise<{
|
|
113
118
|
replacements: LegacySkillReplacement[];
|
|
114
119
|
problems: LegacySkillProblem[];
|
|
115
|
-
}
|
|
120
|
+
}>;
|
|
116
121
|
//# 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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"}
|