@beunec/rivine 0.1.3 → 0.1.5
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/protocol/permissions.js +12 -0
- package/dist/ui/App.js +27 -5
- package/dist/ui/components.js +62 -8
- package/dist/ui/render.js +4 -0
- package/package.json +1 -1
|
@@ -214,6 +214,18 @@ export function nextMode(current) {
|
|
|
214
214
|
return 'default';
|
|
215
215
|
return CYCLE_MODES[(index + 1) % CYCLE_MODES.length];
|
|
216
216
|
}
|
|
217
|
+
export function modeName(mode) {
|
|
218
|
+
switch (mode) {
|
|
219
|
+
case 'plan':
|
|
220
|
+
return 'plan';
|
|
221
|
+
case 'default':
|
|
222
|
+
return 'default';
|
|
223
|
+
case 'acceptEdits':
|
|
224
|
+
return 'accept edits';
|
|
225
|
+
case 'bypass':
|
|
226
|
+
return 'bypass';
|
|
227
|
+
}
|
|
228
|
+
}
|
|
217
229
|
export function describeMode(mode) {
|
|
218
230
|
switch (mode) {
|
|
219
231
|
case 'plan':
|
package/dist/ui/App.js
CHANGED
|
@@ -23,13 +23,14 @@ import { diff as gitDiff, gitBranchLine, prCreate } from '../protocol/git.js';
|
|
|
23
23
|
import { Activity, ApprovalPrompt, Banner, ModePicker, WORDMARK_MIN_COLUMNS, ChoicePrompt, Completions, HelpPanel, StatusBar, TranscriptLine, ShellPane, SHELL_PANE_LINES, } from './components.js';
|
|
24
24
|
import { TokenBuffer, terminalWidth } from './render.js';
|
|
25
25
|
import { color } from './theme.js';
|
|
26
|
-
import { completionsFor } from './slashCommands.js';
|
|
26
|
+
import { completeInput, completionsFor } from './slashCommands.js';
|
|
27
27
|
function trimToPane(text) {
|
|
28
28
|
const lines = text.split('\n');
|
|
29
29
|
return lines.length > SHELL_PANE_LINES * 2
|
|
30
30
|
? lines.slice(-SHELL_PANE_LINES * 2).join('\n')
|
|
31
31
|
: text;
|
|
32
32
|
}
|
|
33
|
+
const ESC = String.fromCharCode(27);
|
|
33
34
|
function grantKey(category, request) {
|
|
34
35
|
if (category !== 'mcp')
|
|
35
36
|
return category;
|
|
@@ -48,6 +49,14 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
48
49
|
const [tool, setTool] = useState('');
|
|
49
50
|
const [mode, setMode] = useState(initialMode);
|
|
50
51
|
const [input, setInput] = useState('');
|
|
52
|
+
const [inputSeed, setInputSeed] = useState('');
|
|
53
|
+
const [inputNonce, setInputNonce] = useState(0);
|
|
54
|
+
const [screenGeneration, setScreenGeneration] = useState(0);
|
|
55
|
+
const setPromptText = useCallback((text) => {
|
|
56
|
+
setInput(text);
|
|
57
|
+
setInputSeed(text);
|
|
58
|
+
setInputNonce((n) => n + 1);
|
|
59
|
+
}, []);
|
|
51
60
|
const [showHelp, setShowHelp] = useState(false);
|
|
52
61
|
const [approval, setApproval] = useState(null);
|
|
53
62
|
const [question, setQuestion] = useState(null);
|
|
@@ -114,6 +123,9 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
114
123
|
}
|
|
115
124
|
push('system', 'Ask for anything. /help for commands.');
|
|
116
125
|
}, [push]);
|
|
126
|
+
const cancelModePick = useCallback(() => {
|
|
127
|
+
setPhase(sessionReadyRef.current ? 'ready' : 'starting');
|
|
128
|
+
}, []);
|
|
117
129
|
const quit = useCallback(() => {
|
|
118
130
|
void runnerRef.current?.shutdown().finally(() => exit());
|
|
119
131
|
}, [exit]);
|
|
@@ -137,10 +149,13 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
137
149
|
quit();
|
|
138
150
|
return true;
|
|
139
151
|
case '/clear':
|
|
152
|
+
process.stdout.write(ESC + '[2J' + ESC + '[3J' + ESC + '[H');
|
|
140
153
|
setLines([]);
|
|
154
|
+
setShowHelp(false);
|
|
155
|
+
setScreenGeneration((n) => n + 1);
|
|
141
156
|
return true;
|
|
142
157
|
case '/mode':
|
|
143
|
-
|
|
158
|
+
setPhase('mode');
|
|
144
159
|
return true;
|
|
145
160
|
case '/plan':
|
|
146
161
|
setMode('plan');
|
|
@@ -201,7 +216,7 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
201
216
|
}, [cwd, cycleMode, mode, push, quit, refreshBalance, status]);
|
|
202
217
|
const submit = useCallback(async (message) => {
|
|
203
218
|
const text = message.trim();
|
|
204
|
-
|
|
219
|
+
setPromptText('');
|
|
205
220
|
if (!runnerRef.current)
|
|
206
221
|
return;
|
|
207
222
|
if (runnerRef.current.interactiveShell) {
|
|
@@ -418,6 +433,12 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
418
433
|
cycleMode();
|
|
419
434
|
return;
|
|
420
435
|
}
|
|
436
|
+
if (key.tab) {
|
|
437
|
+
const completed = completeInput(input);
|
|
438
|
+
if (completed !== input)
|
|
439
|
+
setPromptText(completed);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
421
442
|
if (key.escape && runnerRef.current?.killShell()) {
|
|
422
443
|
push('notice', 'Stopped the running command.');
|
|
423
444
|
return;
|
|
@@ -459,15 +480,16 @@ export function App({ cwd, initialMode, askForMode, oneShot }) {
|
|
|
459
480
|
push(answer.approved ? 'system' : 'notice', answer.approved ? 'Approved.' : 'Declined.');
|
|
460
481
|
resolve?.(answer);
|
|
461
482
|
}, [push]);
|
|
483
|
+
const staticItems = useMemo(() => [{ kind: 'banner' }, ...lines.map((line) => ({ kind: 'line', line }))], [lines]);
|
|
462
484
|
if (phase === 'fatal') {
|
|
463
485
|
return (_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { color: color('danger'), children: fatal }) }));
|
|
464
486
|
}
|
|
465
487
|
const compactBanner = terminalWidth() < WORDMARK_MIN_COLUMNS || Boolean(oneShot);
|
|
466
488
|
const matches = completionsFor(input);
|
|
467
489
|
const busy = phase !== 'ready';
|
|
468
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items:
|
|
490
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: staticItems, children: (item) => item.kind === 'banner' ? (_jsx(Banner, { compact: compactBanner }, "banner")) : (_jsx(TranscriptLine, { line: item.line }, item.line.id)) }, screenGeneration), live ? (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: color('text'), children: live }) })) : null, shellView ? _jsx(ShellPane, { text: shellView, live: shellActive }) : null, phase === 'running' && stage ? _jsx(Activity, { stage: stage, tool: tool }) : null, phase === 'mode' ? (_jsx(ModePicker, { onPick: pickMode, current: modeChosenRef.current ? mode : undefined, onCancel: modeChosenRef.current ? cancelModePick : undefined })) : null, phase === 'approval' && approval ? (_jsx(ApprovalPrompt, { approval: approval, allowAlways: isBatchable(approval.category), onChoice: answerApproval })) : null, phase === 'question' && question ? (question.options?.length ? (_jsx(ChoicePrompt, { prompt: question.prompt, options: question.options, onPick: answerQuestion })) : (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('accent'), bold: true, children: question.prompt }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: color('user'), children: '› ' }), _jsx(TextInput, { placeholder: "your answer", onSubmit: answerQuestion })] })] }))) : null, phase === 'hitl' && hitl ? (_jsx(ChoicePrompt, { prompt: hitl.tool ? `${hitl.tool} — ${hitl.message}` : hitl.message, options: hitl.options?.length ? hitl.options : ['Approve', 'Decline'], onPick: (value) => answerHitl(hitl.options?.length
|
|
469
491
|
? { approved: true, feedback: value }
|
|
470
|
-
: { approved: value === 'Approve' }) })) : null, showHelp && phase === 'ready' ? _jsx(HelpPanel, {}) : null, phase === 'ready' && !oneShot ? (_jsxs(_Fragment, { children: [_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: color('user'), children: '› ' }), _jsx(TextInput, { placeholder: "What should Rivine build?", onChange: setInput, onSubmit: (value) => void submit(value) })] }), _jsx(Completions, { matches: matches })] })) : null, phase === 'starting' ? _jsx(Activity, { stage: "Starting\u2026", tool: "" }) : null, _jsx(StatusBar, { info: {
|
|
492
|
+
: { approved: value === 'Approve' }) })) : null, showHelp && phase === 'ready' ? _jsx(HelpPanel, {}) : null, phase === 'ready' && !oneShot ? (_jsxs(_Fragment, { children: [_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: color('user'), children: '› ' }), _jsx(TextInput, { defaultValue: inputSeed, placeholder: "What should Rivine build?", onChange: setInput, onSubmit: (value) => void submit(value) }, inputNonce)] }), _jsx(Completions, { matches: matches })] })) : null, phase === 'starting' ? _jsx(Activity, { stage: "Starting\u2026", tool: "" }) : null, _jsx(StatusBar, { info: {
|
|
471
493
|
mode,
|
|
472
494
|
project: status.project,
|
|
473
495
|
cloudhandle: status.cloudhandle,
|
package/dist/ui/components.js
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
* redistribution or derivative works.
|
|
10
10
|
*/
|
|
11
11
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
|
+
import React from 'react';
|
|
12
13
|
import { Box, Text, useInput } from 'ink';
|
|
13
14
|
import { Spinner } from '@inkjs/ui';
|
|
14
15
|
import { ASCII_ONLY, BOX, color, glyph, lineColor } from './theme.js';
|
|
15
|
-
import { fit, terminalWidth, wrap } from './render.js';
|
|
16
|
+
import { fit, terminalWidth, wrap, terminalColumns } from './render.js';
|
|
16
17
|
import { SLASH_COMMANDS } from './slashCommands.js';
|
|
17
|
-
import { describeMode } from '../protocol/permissions.js';
|
|
18
|
+
import { describeMode, modeName } from '../protocol/permissions.js';
|
|
18
19
|
const WORDMARK = [
|
|
19
20
|
'█▀▄ ▀█▀ █ █ ▀█▀ █▄ █ █▀▀ █▀▀ █▀█ █▀▄ █▀▀',
|
|
20
21
|
'█▀▄ █ ▀▄ ▄▀ █ █ ▀█ █▀ █ █ █ █ █ █▀ ',
|
|
@@ -39,7 +40,7 @@ export function Activity({ stage, tool }) {
|
|
|
39
40
|
const label = tool ? `${stage} · ${tool}` : stage;
|
|
40
41
|
return (_jsx(Box, { paddingX: 1, children: _jsx(Spinner, { label: fit(label, width - 6) }) }));
|
|
41
42
|
}
|
|
42
|
-
export function ModePicker({ onPick }) {
|
|
43
|
+
export function ModePicker({ onPick, current, onCancel, }) {
|
|
43
44
|
const width = terminalWidth();
|
|
44
45
|
const options = [
|
|
45
46
|
{ key: '1', mode: 'plan', label: 'Plan', detail: 'reads and answers, changes nothing' },
|
|
@@ -48,15 +49,25 @@ export function ModePicker({ onPick }) {
|
|
|
48
49
|
{ key: '4', mode: 'bypass', label: 'Full speed', detail: 'no prompts inside this project' },
|
|
49
50
|
];
|
|
50
51
|
useInput((input, key) => {
|
|
52
|
+
if (key.escape && onCancel) {
|
|
53
|
+
onCancel();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
if (key.return) {
|
|
52
|
-
onPick('acceptEdits');
|
|
57
|
+
onPick(current ?? 'acceptEdits');
|
|
53
58
|
return;
|
|
54
59
|
}
|
|
55
60
|
const found = options.find((o) => o.key === input);
|
|
56
61
|
if (found)
|
|
57
62
|
onPick(found.mode);
|
|
58
63
|
});
|
|
59
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "How should Rivine work in this project?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((o) =>
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "How should Rivine work in this project?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((o) => {
|
|
65
|
+
const active = o.mode === current;
|
|
66
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: active ? color('success') : color('faint'), children: active ? ' ›' : ' ' }), _jsx(Text, { color: color('accent'), children: o.key }), _jsx(Text, { color: color('faint'), children: " \u00B7 " }), _jsx(Text, { color: active ? color('text') : color('muted'), bold: active, children: o.label.padEnd(13) }), _jsx(Text, { color: color('muted'), children: fit(o.detail, Math.max(10, width - 26)) })] }, o.key));
|
|
67
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: color('faint'), children: fit(current
|
|
68
|
+
?
|
|
69
|
+
`press a number · enter keeps ${options.find((o) => o.mode === current)?.label ?? 'this'} · esc cancels`
|
|
70
|
+
: 'press a number · enter for accept edits · shift+tab changes it later', width - 4) }) })] }));
|
|
60
71
|
}
|
|
61
72
|
export const SHELL_PANE_LINES = 12;
|
|
62
73
|
export function ShellPane({ text, live }) {
|
|
@@ -104,8 +115,51 @@ export function HelpPanel() {
|
|
|
104
115
|
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "Commands" }), SLASH_COMMANDS.map((c) => (_jsxs(Box, { children: [_jsx(Text, { color: color('accent'), children: ` ${c.name}`.padEnd(12) }), _jsx(Text, { color: color('muted'), children: c.summary })] }, c.name))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: color('faint'), children: " shift+tab change permission mode" }), _jsx(Text, { color: color('faint'), children: " esc stop the current turn" }), _jsx(Text, { color: color('faint'), children: " ctrl+c quit" })] })] }));
|
|
105
116
|
}
|
|
106
117
|
export function StatusBar({ info }) {
|
|
107
|
-
const width =
|
|
108
|
-
const narrow = width < 76;
|
|
118
|
+
const width = terminalColumns();
|
|
109
119
|
const dot = info.busy ? color('warn') : color('success');
|
|
110
|
-
|
|
120
|
+
const segments = [];
|
|
121
|
+
let used = 2;
|
|
122
|
+
let full = true;
|
|
123
|
+
const add = (text, node) => {
|
|
124
|
+
if (!full)
|
|
125
|
+
return false;
|
|
126
|
+
const cost = (segments.length > 0 ? 3 : 0) + text.length;
|
|
127
|
+
if (used + cost > width - 2) {
|
|
128
|
+
full = false;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
used += cost;
|
|
132
|
+
segments.push({ node, text });
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
const modeText = describeMode(info.mode);
|
|
136
|
+
const modeShort = modeName(info.mode);
|
|
137
|
+
const brand = 'Rivine';
|
|
138
|
+
const fitsBrandAndFullMode = 2 + brand.length + 3 + modeText.length <= width - 2;
|
|
139
|
+
if (2 + brand.length + 3 + modeShort.length <= width - 2) {
|
|
140
|
+
add(brand, (_jsx(Text, { color: color('brand'), bold: true, children: brand })));
|
|
141
|
+
}
|
|
142
|
+
const shownMode = fitsBrandAndFullMode ? modeText : modeShort;
|
|
143
|
+
used += (segments.length > 0 ? 3 : 0) + shownMode.length;
|
|
144
|
+
segments.push({
|
|
145
|
+
node: _jsx(Text, { color: color('muted'), children: shownMode }),
|
|
146
|
+
text: shownMode,
|
|
147
|
+
});
|
|
148
|
+
if (info.balance !== null) {
|
|
149
|
+
const money = `$${info.balance.toFixed(2)}`;
|
|
150
|
+
add(money, _jsx(Text, { color: info.balance < 10 ? color('warn') : color('muted'), children: money }));
|
|
151
|
+
}
|
|
152
|
+
if (info.project) {
|
|
153
|
+
const project = fit(info.project, 18, true);
|
|
154
|
+
add(project, _jsx(Text, { color: color('muted'), children: project }));
|
|
155
|
+
}
|
|
156
|
+
if (info.cloudhandle) {
|
|
157
|
+
const handle = fit(info.cloudhandle, 22);
|
|
158
|
+
const masked = info.bcidMasked ? ` ${info.bcidMasked}` : '';
|
|
159
|
+
add(handle + masked, _jsxs(_Fragment, { children: [_jsx(Text, { color: color('brand'), children: handle }), masked ? _jsx(Text, { color: color('faint'), children: masked }) : null] }));
|
|
160
|
+
}
|
|
161
|
+
if (used + 8 <= width - 2) {
|
|
162
|
+
segments.push({ node: _jsx(Text, { color: color('faint'), children: ' /help' }), text: ' /help' });
|
|
163
|
+
}
|
|
164
|
+
return (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: dot, children: [BOX.dot, " "] }), segments.map((segment, i) => (_jsxs(React.Fragment, { children: [i > 0 && segment.text !== ' /help' ? (_jsx(Text, { color: color('faint'), children: " \u00B7 " })) : null, segment.node] }, i)))] }));
|
|
111
165
|
}
|
package/dist/ui/render.js
CHANGED
|
@@ -49,6 +49,10 @@ export class TokenBuffer {
|
|
|
49
49
|
this.pending = '';
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
export function terminalColumns() {
|
|
53
|
+
const columns = process.stdout.columns;
|
|
54
|
+
return Math.max(20, columns && columns > 0 ? columns : 80);
|
|
55
|
+
}
|
|
52
56
|
export function terminalWidth() {
|
|
53
57
|
const columns = process.stdout.columns;
|
|
54
58
|
return Math.max(48, Math.min(columns && columns > 0 ? columns : 80, 120));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beunec/rivine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Rivine Code in your terminal — Beunec's software engineering runtime constructs, working on the files you already have open.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Beunec Technologies",
|