@beunec/rivine 0.2.0 → 0.3.0
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/README.md +16 -10
- package/dist/api/workspace.js +9 -1
- package/dist/index.js +21 -8
- package/dist/protocol/permissions.js +17 -9
- package/dist/protocol/runner.js +38 -3
- package/dist/runHeadless.js +5 -2
- package/dist/ui/App.js +82 -23
- package/dist/ui/components.js +26 -18
- package/dist/ui/headless.js +7 -4
- package/dist/ui/notices.js +20 -0
- package/dist/ui/phase.js +28 -0
- package/dist/ui/render.js +3 -0
- package/dist/ui/slashCommands.js +18 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,11 +60,11 @@ no router — no keyword matcher, no classifier, no extra model turn that reads
|
|
|
60
60
|
your request and picks a lane. A wrong guess sends a database migration to the
|
|
61
61
|
frontend agent and you never see the decision that was made for you.
|
|
62
62
|
|
|
63
|
-
### Mode —
|
|
63
|
+
### Mode — what Rivine Code works on
|
|
64
64
|
|
|
65
|
-
Each Mode
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
Each Mode owns the task end to end: it searches the codebase, reads what it
|
|
66
|
+
needs, writes across as many files as the change requires, runs the build and
|
|
67
|
+
tests, and fixes what the terminal reports.
|
|
68
68
|
|
|
69
69
|
| Mode | For | How to choose |
|
|
70
70
|
|---|---|---|
|
|
@@ -93,9 +93,14 @@ even having said "just do it": credentials, anything outside the project
|
|
|
93
93
|
directory, and commands that are hard to undo (`rm -rf`, `git push --force`, a
|
|
94
94
|
deploy).
|
|
95
95
|
|
|
96
|
-
The first interactive session asks for Mode, then Rate
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
The first interactive session asks for Mode, then Rate, before the prompt
|
|
97
|
+
opens — including for a one-shot `rivine "..."`, which used to assume Full
|
|
98
|
+
Stack. `-m …` and `--rate …` skip the question you already answered on the
|
|
99
|
+
command line. Shift+Tab toggles the Rate. `/mode` and `/rate` reopen either
|
|
100
|
+
picker later.
|
|
101
|
+
|
|
102
|
+
Without a terminal to ask on — a pipe, CI, another agent driving this — `-m`
|
|
103
|
+
is required and the run stops with the list rather than guessing.
|
|
99
104
|
|
|
100
105
|
What stays gated:
|
|
101
106
|
|
|
@@ -138,9 +143,10 @@ rivine mcp login <name> sign in to a remote MCP server
|
|
|
138
143
|
rivine --output-format json one JSON object on stdout, for CI
|
|
139
144
|
```
|
|
140
145
|
|
|
141
|
-
In a session: type `/` and press Tab. `/help` `/mode` `/plan` `/accept`
|
|
142
|
-
`/status` `/diff` `/pr` `/mcp` `/credits` `/clear` `/exit`.
|
|
143
|
-
|
|
146
|
+
In a session: type `/` and press Tab. `/help` `/mode` `/rate` `/plan` `/accept`
|
|
147
|
+
`/status` `/diff` `/pr` `/mcp` `/credits` `/clear` `/exit`. Startup asks for
|
|
148
|
+
**Mode** (what Rivine Code works on) then **Rate** (Manual or Bypass); `/mode`
|
|
149
|
+
and `/rate` reopen those pickers. Shift+Tab toggles Manual ⇄ Bypass. Ctrl+C
|
|
144
150
|
quits cleanly.
|
|
145
151
|
|
|
146
152
|
`/diff` shows what changed in the working tree. `/pr` prepares a pull request
|
package/dist/api/workspace.js
CHANGED
|
@@ -120,12 +120,20 @@ export async function resumeTurn(args) {
|
|
|
120
120
|
decision: args.approved ? 'approved' : 'rejected',
|
|
121
121
|
feedback: args.feedback ?? '',
|
|
122
122
|
modified_args: args.modifiedArgs ?? null,
|
|
123
|
+
code_mode: args.codeMode ?? null,
|
|
124
|
+
code_rate: args.codeRate ?? null,
|
|
123
125
|
cli_session_key: args.sessionKey ?? null,
|
|
124
126
|
cli_session_id: args.sessionId,
|
|
125
127
|
}),
|
|
126
128
|
});
|
|
127
129
|
if (!res.ok) {
|
|
128
|
-
|
|
130
|
+
const detail = await res
|
|
131
|
+
.json()
|
|
132
|
+
.then((b) => String(b?.detail ?? b?.error ?? '').trim())
|
|
133
|
+
.catch(() => '');
|
|
134
|
+
throw new Error(detail
|
|
135
|
+
? `Could not resume the turn (HTTP ${res.status}): ${detail}`
|
|
136
|
+
: `Could not resume the turn (HTTP ${res.status}).`);
|
|
129
137
|
}
|
|
130
138
|
return (await res.json());
|
|
131
139
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* redistribution or derivative works.
|
|
11
11
|
*/
|
|
12
12
|
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { realpath } from 'node:fs/promises';
|
|
13
14
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
15
16
|
import { render } from 'ink';
|
|
@@ -18,6 +19,7 @@ import { Command } from 'commander';
|
|
|
18
19
|
import { App } from './ui/App.js';
|
|
19
20
|
import { runHeadless } from './runHeadless.js';
|
|
20
21
|
import { isInteractiveTerminal, installTeardown } from './ui/headless.js';
|
|
22
|
+
import { emitNotice } from './ui/notices.js';
|
|
21
23
|
import { login, logout, status } from './auth/session.js';
|
|
22
24
|
import { openBrowser } from './auth/pkce.js';
|
|
23
25
|
import { apiUrl, siteUrl } from './config.js';
|
|
@@ -262,14 +264,15 @@ function collect(value, previous) {
|
|
|
262
264
|
program
|
|
263
265
|
.argument('[prompt...]', 'send one prompt and exit')
|
|
264
266
|
.option('-p, --print <prompt>', 'send one prompt, print the answer, and exit')
|
|
265
|
-
.option('-m, --mode <mode>', `
|
|
266
|
-
.option('--rate <rate>', `how edits apply: ${RATES.join(' | ')}
|
|
267
|
+
.option('-m, --mode <mode>', `what Rivine Code works on: ${CODE_MODES.join(' | ')}`)
|
|
268
|
+
.option('--rate <rate>', `how edits apply: ${RATES.join(' | ')}`)
|
|
267
269
|
.option('--plan', 'shorthand for --mode simple (Plan/Ask Only)')
|
|
268
270
|
.option('--bypass', 'never ask for approval — for CI only, on a machine you are willing to have changed')
|
|
269
271
|
.option('-C, --cwd <dir>', 'run against a different directory')
|
|
270
272
|
.option('--output-format <format>', 'text | json — json puts one object on stdout and everything else on stderr', 'text')
|
|
271
273
|
.action(async (prompt, options) => {
|
|
272
|
-
const
|
|
274
|
+
const requested = options.cwd ? resolvePath(options.cwd) : process.cwd();
|
|
275
|
+
const cwd = await realpath(requested).catch(() => requested);
|
|
273
276
|
const oneShot = options.print ?? (prompt.length ? prompt.join(' ') : undefined);
|
|
274
277
|
const { mode, explicit: modeExplicit } = resolveMode(options);
|
|
275
278
|
const codeMode = resolveCodeMode(options);
|
|
@@ -298,19 +301,29 @@ program
|
|
|
298
301
|
return;
|
|
299
302
|
}
|
|
300
303
|
if (format === 'json' && oneShot) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
if (!codeMode) {
|
|
305
|
+
console.error('\n --mode is required with --output-format json.\n' +
|
|
306
|
+
` Choose one of: ${CODE_MODES.join(', ')}\n` +
|
|
307
|
+
' e.g. rivine -m backend --output-format json -p "add tests for auth.ts"\n');
|
|
308
|
+
process.exitCode = 2;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
process.exitCode = await runHeadless({ cwd, mode, codeMode, prompt: oneShot, format });
|
|
304
312
|
return;
|
|
305
313
|
}
|
|
306
314
|
const { waitUntilExit } = render(React.createElement(App, {
|
|
307
315
|
cwd,
|
|
308
316
|
initialMode: mode,
|
|
309
317
|
askForMode: !modeExplicit && !oneShot,
|
|
310
|
-
initialCodeMode: codeMode ??
|
|
318
|
+
initialCodeMode: codeMode ?? undefined,
|
|
311
319
|
oneShot,
|
|
312
320
|
}), { exitOnCtrlC: false });
|
|
313
|
-
const removeTeardown = installTeardown(() => { }
|
|
321
|
+
const removeTeardown = installTeardown(() => { }, {
|
|
322
|
+
onRejection: (err) => {
|
|
323
|
+
const message = err?.message ?? String(err);
|
|
324
|
+
emitNotice(`Background task failed: ${message}`);
|
|
325
|
+
},
|
|
326
|
+
});
|
|
314
327
|
try {
|
|
315
328
|
await waitUntilExit();
|
|
316
329
|
}
|
|
@@ -47,7 +47,6 @@ export function parseCodeMode(raw) {
|
|
|
47
47
|
return table[key] ?? null;
|
|
48
48
|
}
|
|
49
49
|
export const PERMISSION_MODES = ['plan', 'default', 'acceptEdits', 'bypass'];
|
|
50
|
-
export const CYCLE_MODES = ['plan', 'default', 'acceptEdits'];
|
|
51
50
|
export function isBatchable(category) {
|
|
52
51
|
return category === 'write' || category === 'mcp';
|
|
53
52
|
}
|
|
@@ -251,17 +250,13 @@ export function nextMode(current) {
|
|
|
251
250
|
? rateToPermissionMode('manual')
|
|
252
251
|
: rateToPermissionMode('bypass');
|
|
253
252
|
}
|
|
254
|
-
export function nextRate(current) {
|
|
255
|
-
return current === 'bypass' ? 'manual' : 'bypass';
|
|
256
|
-
}
|
|
257
253
|
export function modeName(mode) {
|
|
258
254
|
switch (mode) {
|
|
259
255
|
case 'plan':
|
|
260
256
|
return 'plan';
|
|
261
257
|
case 'default':
|
|
262
|
-
return 'default';
|
|
263
258
|
case 'acceptEdits':
|
|
264
|
-
return '
|
|
259
|
+
return 'manual';
|
|
265
260
|
case 'bypass':
|
|
266
261
|
return 'bypass';
|
|
267
262
|
}
|
|
@@ -271,10 +266,23 @@ export function describeMode(mode) {
|
|
|
271
266
|
case 'plan':
|
|
272
267
|
return 'plan · reads only';
|
|
273
268
|
case 'default':
|
|
274
|
-
return 'default · asks before changes';
|
|
275
269
|
case 'acceptEdits':
|
|
276
|
-
return '
|
|
270
|
+
return 'manual · asks before edits';
|
|
277
271
|
case 'bypass':
|
|
278
|
-
return 'bypass ·
|
|
272
|
+
return 'bypass · edits without asking';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function codeModeName(mode) {
|
|
276
|
+
switch (mode) {
|
|
277
|
+
case 'frontend':
|
|
278
|
+
return 'Frontend';
|
|
279
|
+
case 'backend':
|
|
280
|
+
return 'Backend';
|
|
281
|
+
case 'fullstack':
|
|
282
|
+
return 'Full Stack';
|
|
283
|
+
case 'simple':
|
|
284
|
+
return 'Plan/Ask';
|
|
285
|
+
case 'security':
|
|
286
|
+
return 'Security';
|
|
279
287
|
}
|
|
280
288
|
}
|
package/dist/protocol/runner.js
CHANGED
|
@@ -16,6 +16,7 @@ import { isSealedEnvelope, SessionKey } from './sessionKey.js';
|
|
|
16
16
|
import { permissionModeToRate } from './permissions.js';
|
|
17
17
|
import { selectRelevantTools } from '../mcp/relevance.js';
|
|
18
18
|
import { readTurnStream } from './stream.js';
|
|
19
|
+
import { displayStage } from '../ui/render.js';
|
|
19
20
|
const PENDING_POLL_MS = 1200;
|
|
20
21
|
export class TurnRunner {
|
|
21
22
|
ctx;
|
|
@@ -72,6 +73,14 @@ export class TurnRunner {
|
|
|
72
73
|
this.ctx.mcpTools = tools;
|
|
73
74
|
}
|
|
74
75
|
async run(message, snapshot) {
|
|
76
|
+
if (!this.ctx.codeMode) {
|
|
77
|
+
return {
|
|
78
|
+
answer: '',
|
|
79
|
+
cancelled: false,
|
|
80
|
+
error: 'No Mode selected. Pick one with /mode — Frontend Only, Backend Only, ' +
|
|
81
|
+
'Full Stack, Plan/Ask Only, or Security Scan/Fix.',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
75
84
|
this.stopped = false;
|
|
76
85
|
this.startPolling();
|
|
77
86
|
const selection = selectRelevantTools(message, this.ctx.mcpTools ?? []);
|
|
@@ -112,9 +121,12 @@ export class TurnRunner {
|
|
|
112
121
|
answer += event.content ?? '';
|
|
113
122
|
this.cb.onToken(event.content ?? '');
|
|
114
123
|
break;
|
|
115
|
-
case 'stage':
|
|
116
|
-
|
|
124
|
+
case 'stage': {
|
|
125
|
+
const label = displayStage(String(event.label ?? ''));
|
|
126
|
+
if (label)
|
|
127
|
+
this.cb.onStage(label);
|
|
117
128
|
break;
|
|
129
|
+
}
|
|
118
130
|
case 'tool_call_start': {
|
|
119
131
|
const e = event;
|
|
120
132
|
const detail = e.args?.command ?? e.args?.filename ?? '';
|
|
@@ -158,6 +170,7 @@ export class TurnRunner {
|
|
|
158
170
|
options: extractOptions(e),
|
|
159
171
|
multi: Boolean(e.ui_schema?.multi ?? e.args?.multi),
|
|
160
172
|
threadId: String(e.thread_id ?? `${this.ctx.workspaceId}_${this.ctx.chatSpaceId}`),
|
|
173
|
+
kind: hitlKind(e.hitl_type, e.args),
|
|
161
174
|
};
|
|
162
175
|
break;
|
|
163
176
|
}
|
|
@@ -184,6 +197,8 @@ export class TurnRunner {
|
|
|
184
197
|
let answer = '';
|
|
185
198
|
for (let round = 0; current && round < 25; round += 1) {
|
|
186
199
|
const decision = await this.enqueuePrompt(() => this.cb.requestDecision(current));
|
|
200
|
+
if (decision.codeMode)
|
|
201
|
+
this.setCodeMode(decision.codeMode);
|
|
187
202
|
const body = (await resumeTurn({
|
|
188
203
|
workspaceId: this.ctx.workspaceId,
|
|
189
204
|
chatSpaceId: this.ctx.chatSpaceId,
|
|
@@ -193,6 +208,8 @@ export class TurnRunner {
|
|
|
193
208
|
approved: decision.approved,
|
|
194
209
|
feedback: decision.feedback,
|
|
195
210
|
modifiedArgs: decision.modifiedArgs,
|
|
211
|
+
codeMode: decision.codeMode ?? null,
|
|
212
|
+
codeRate: decision.codeMode ? permissionModeToRate(this.ctx.mode) : null,
|
|
196
213
|
}));
|
|
197
214
|
answer = String(body.response ?? answer);
|
|
198
215
|
const meta = body.context_metadata ?? {};
|
|
@@ -203,6 +220,7 @@ export class TurnRunner {
|
|
|
203
220
|
options: extractOptions({ ui_schema: meta.hitl_ui_schema, args: meta.hitl_args }),
|
|
204
221
|
multi: Boolean(meta.hitl_ui_schema?.multi),
|
|
205
222
|
threadId: String(meta.thread_id ?? current.threadId),
|
|
223
|
+
kind: hitlKind(meta.hitl_type, meta.hitl_args),
|
|
206
224
|
}
|
|
207
225
|
: null;
|
|
208
226
|
}
|
|
@@ -339,9 +357,26 @@ export class TurnRunner {
|
|
|
339
357
|
await closeSession(this.ctx.sessionId);
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
|
-
function
|
|
360
|
+
export function hitlKind(hitlType, args) {
|
|
361
|
+
const field = args && typeof args === 'object' ? String(args.field ?? '') : '';
|
|
362
|
+
return String(hitlType ?? '') === 'code_clarification' && field === 'mode'
|
|
363
|
+
? 'code_mode'
|
|
364
|
+
: 'other';
|
|
365
|
+
}
|
|
366
|
+
export function extractOptions(event) {
|
|
367
|
+
const schemaEnum = (() => {
|
|
368
|
+
const props = event?.ui_schema?.properties;
|
|
369
|
+
if (!props || typeof props !== 'object')
|
|
370
|
+
return undefined;
|
|
371
|
+
for (const value of Object.values(props)) {
|
|
372
|
+
if (Array.isArray(value?.enum) && value.enum.length)
|
|
373
|
+
return value.enum;
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
})();
|
|
343
377
|
const raw = event?.ui_schema?.options ??
|
|
344
378
|
event?.ui_schema?.choices ??
|
|
379
|
+
schemaEnum ??
|
|
345
380
|
event?.args?.options ??
|
|
346
381
|
event?.options;
|
|
347
382
|
if (!Array.isArray(raw) || raw.length === 0)
|
package/dist/runHeadless.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { InsufficientCredits } from './api/workspace.js';
|
|
12
12
|
import { siteUrl } from './config.js';
|
|
13
13
|
import { TurnRunner } from './protocol/runner.js';
|
|
14
|
+
import { permissionModeToRate } from './protocol/permissions.js';
|
|
14
15
|
import { creditShortfall, startSession } from './session.js';
|
|
15
16
|
import { HeadlessRenderer, installTeardown } from './ui/headless.js';
|
|
16
17
|
export async function runHeadless(options) {
|
|
@@ -84,7 +85,9 @@ export async function runHeadless(options) {
|
|
|
84
85
|
},
|
|
85
86
|
requestDecision: async (request) => {
|
|
86
87
|
out.endAnswer();
|
|
87
|
-
out.line('notice',
|
|
88
|
+
out.line('notice', request.kind === 'code_mode'
|
|
89
|
+
? 'No Mode selected. Pass -m frontend|backend|fullstack|simple|security.'
|
|
90
|
+
: `Approval needed but no terminal to ask on: ${request.message}`);
|
|
88
91
|
return { approved: false, feedback: 'No interactive terminal available.' };
|
|
89
92
|
},
|
|
90
93
|
askQuestion: async (question) => {
|
|
@@ -111,7 +114,7 @@ export async function runHeadless(options) {
|
|
|
111
114
|
if (out.refusalCount > 0) {
|
|
112
115
|
const advice = options.mode === 'plan'
|
|
113
116
|
? 'plan mode makes no changes — re-run without --plan'
|
|
114
|
-
: `re-run with
|
|
117
|
+
: `re-run with --rate bypass (Rate was ${permissionModeToRate(options.mode)})`;
|
|
115
118
|
const message = `${out.refusalCount} operation(s) were skipped because there is no terminal ` +
|
|
116
119
|
`to approve them: ${advice}.`;
|
|
117
120
|
out.line('error', message);
|
package/dist/ui/App.js
CHANGED
|
@@ -20,10 +20,12 @@ import { CODE_MODE_LABELS, describeMode, isBatchable, mcpServerOf, nextMode, } f
|
|
|
20
20
|
import { creditShortfall, startSession } from '../session.js';
|
|
21
21
|
import { sanitizeForModel } from '../protocol/shell.js';
|
|
22
22
|
import { diff as gitDiff, gitBranchLine, prCreate } from '../protocol/git.js';
|
|
23
|
-
import { Activity, ApprovalPrompt, Banner, CodeModePicker,
|
|
23
|
+
import { Activity, ApprovalPrompt, Banner, CodeModePicker, RatePicker, WORDMARK_MIN_COLUMNS, ChoicePrompt, Completions, HelpPanel, StatusBar, TranscriptLine, ShellPane, SHELL_PANE_LINES, } from './components.js';
|
|
24
24
|
import { TokenBuffer, terminalWidth } from './render.js';
|
|
25
|
+
import { initialPhase, phaseAfterBootstrap, phaseAfterModePicked, phaseAfterRatePicked, } from './phase.js';
|
|
25
26
|
import { color } from './theme.js';
|
|
26
27
|
import { completeInput, completionsFor } from './slashCommands.js';
|
|
28
|
+
import { setNoticeSink } from './notices.js';
|
|
27
29
|
function trimToPane(text) {
|
|
28
30
|
const lines = text.split('\n');
|
|
29
31
|
return lines.length > SHELL_PANE_LINES * 2
|
|
@@ -40,7 +42,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
40
42
|
const { exit } = useApp();
|
|
41
43
|
const [codeMode, setCodeMode] = useState(initialCodeMode);
|
|
42
44
|
const codeModeChosenRef = useRef(Boolean(initialCodeMode));
|
|
43
|
-
const [phase, setPhase] = useState(initialCodeMode
|
|
45
|
+
const [phase, setPhase] = useState(initialPhase(Boolean(initialCodeMode), Boolean(askForMode)));
|
|
44
46
|
const sessionReadyRef = useRef(false);
|
|
45
47
|
const modeChosenRef = useRef(!askForMode);
|
|
46
48
|
const [lines, setLines] = useState([]);
|
|
@@ -74,6 +76,8 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
74
76
|
const mcpRef = useRef(null);
|
|
75
77
|
const snapshotRef = useRef(null);
|
|
76
78
|
const snapshotSentRef = useRef(false);
|
|
79
|
+
const minCreditsRef = useRef({ rivine_code: 10 });
|
|
80
|
+
const queuedPromptRef = useRef(null);
|
|
77
81
|
const answerRef = useRef(null);
|
|
78
82
|
const lineId = useRef(0);
|
|
79
83
|
const liveText = useRef('');
|
|
@@ -119,9 +123,10 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
119
123
|
codeModeChosenRef.current = true;
|
|
120
124
|
runnerRef.current?.setCodeMode(chosen);
|
|
121
125
|
push('system', `Mode: ${CODE_MODE_LABELS[chosen]}`);
|
|
122
|
-
setPhase(
|
|
123
|
-
|
|
124
|
-
:
|
|
126
|
+
setPhase(phaseAfterModePicked({
|
|
127
|
+
sessionReady: sessionReadyRef.current,
|
|
128
|
+
rateChosen: modeChosenRef.current,
|
|
129
|
+
}));
|
|
125
130
|
}, [push]);
|
|
126
131
|
const cancelCodeModePick = useCallback(() => {
|
|
127
132
|
setPhase(sessionReadyRef.current ? 'ready' : 'starting');
|
|
@@ -130,8 +135,11 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
130
135
|
setMode(chosen);
|
|
131
136
|
modeChosenRef.current = true;
|
|
132
137
|
runnerRef.current?.setMode(chosen);
|
|
133
|
-
setPhase(
|
|
134
|
-
|
|
138
|
+
setPhase(phaseAfterRatePicked({
|
|
139
|
+
sessionReady: sessionReadyRef.current,
|
|
140
|
+
rateChosen: true,
|
|
141
|
+
}));
|
|
142
|
+
push('system', `Rate: ${describeMode(chosen)}`);
|
|
135
143
|
if (chosen === 'bypass') {
|
|
136
144
|
push('notice', 'Running without prompts. Files outside this project are still refused.');
|
|
137
145
|
}
|
|
@@ -140,13 +148,21 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
140
148
|
const cancelModePick = useCallback(() => {
|
|
141
149
|
setPhase(sessionReadyRef.current ? 'ready' : 'starting');
|
|
142
150
|
}, []);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
setNoticeSink((text) => push('notice', text));
|
|
153
|
+
return () => setNoticeSink(null);
|
|
154
|
+
}, [push]);
|
|
143
155
|
const quit = useCallback(() => {
|
|
144
|
-
void runnerRef.current
|
|
156
|
+
void runnerRef.current
|
|
157
|
+
?.shutdown()
|
|
158
|
+
.catch(() => {
|
|
159
|
+
})
|
|
160
|
+
.finally(() => exit());
|
|
145
161
|
}, [exit]);
|
|
146
162
|
const cycleMode = useCallback(() => {
|
|
147
163
|
setMode((current) => {
|
|
148
164
|
const next = nextMode(current);
|
|
149
|
-
push('system', `
|
|
165
|
+
push('system', `Rate: ${describeMode(next)}`);
|
|
150
166
|
return next;
|
|
151
167
|
});
|
|
152
168
|
}, [push]);
|
|
@@ -169,15 +185,26 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
169
185
|
setScreenGeneration((n) => n + 1);
|
|
170
186
|
return true;
|
|
171
187
|
case '/mode':
|
|
188
|
+
setPhase('codeMode');
|
|
189
|
+
return true;
|
|
190
|
+
case '/rate':
|
|
172
191
|
setPhase('mode');
|
|
173
192
|
return true;
|
|
174
193
|
case '/plan':
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
setCodeMode('simple');
|
|
195
|
+
codeModeChosenRef.current = true;
|
|
196
|
+
runnerRef.current?.setCodeMode('simple');
|
|
197
|
+
push('system', `Mode: ${CODE_MODE_LABELS.simple}`);
|
|
198
|
+
setPhase(phaseAfterModePicked({
|
|
199
|
+
sessionReady: sessionReadyRef.current,
|
|
200
|
+
rateChosen: modeChosenRef.current,
|
|
201
|
+
}));
|
|
177
202
|
return true;
|
|
178
203
|
case '/accept':
|
|
179
|
-
setMode('
|
|
180
|
-
|
|
204
|
+
setMode('bypass');
|
|
205
|
+
runnerRef.current?.setMode('bypass');
|
|
206
|
+
push('system', `Rate: ${describeMode('bypass')}`);
|
|
207
|
+
push('notice', 'Accept-edits is gone — Bypass applies in-project edits without asking.');
|
|
181
208
|
return true;
|
|
182
209
|
case '/credits':
|
|
183
210
|
openBrowser(`${siteUrl()}/credits`);
|
|
@@ -197,7 +224,8 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
197
224
|
}
|
|
198
225
|
case '/status': {
|
|
199
226
|
push('system', `${status.cloudhandle || 'signed in'} ${status.bcidMasked} · ${status.project} · ` +
|
|
200
|
-
`${status.balance !== null ? `${status.balance.toFixed(2)}` : 'balance unknown'} ·
|
|
227
|
+
`${status.balance !== null ? `${status.balance.toFixed(2)}` : 'balance unknown'} · ` +
|
|
228
|
+
`${codeMode ? CODE_MODE_LABELS[codeMode] : 'Mode unset'} · ${describeMode(mode)}`);
|
|
201
229
|
const facts = sessionFactsRef.current;
|
|
202
230
|
push('system', `${facts.continuing ? "continuing this directory's thread" : 'new thread'} · ` +
|
|
203
231
|
`${facts.hasLocalMemory ? 'project notes on disk' : 'no project notes yet'}`);
|
|
@@ -227,7 +255,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
227
255
|
push('notice', `Unknown command ${cmd} — /help for the list.`);
|
|
228
256
|
return true;
|
|
229
257
|
}
|
|
230
|
-
}, [cwd, cycleMode, mode, push, quit, refreshBalance, status]);
|
|
258
|
+
}, [codeMode, cwd, cycleMode, mode, push, quit, refreshBalance, status]);
|
|
231
259
|
const submit = useCallback(async (message) => {
|
|
232
260
|
const text = message.trim();
|
|
233
261
|
setPromptText('');
|
|
@@ -241,7 +269,13 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
241
269
|
return;
|
|
242
270
|
if (runCommand(text))
|
|
243
271
|
return;
|
|
244
|
-
|
|
272
|
+
if (!codeMode) {
|
|
273
|
+
queuedPromptRef.current = text;
|
|
274
|
+
push('notice', 'Pick a Mode first — it decides what Rivine Code works on. Your message is saved.');
|
|
275
|
+
setPhase('codeMode');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const needed = creditShortfall(status.balance, minCreditsRef.current);
|
|
245
279
|
if (needed !== null) {
|
|
246
280
|
push('error', `Rivine Code needs $${needed.toFixed(2)} in credits to run.`);
|
|
247
281
|
push('notice', `Top up at ${siteUrl()}/credits`);
|
|
@@ -280,7 +314,16 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
280
314
|
if (oneShot)
|
|
281
315
|
quit();
|
|
282
316
|
}
|
|
283
|
-
}, [oneShot, push, quit, refreshBalance, runCommand, settleLive, status.balance]);
|
|
317
|
+
}, [codeMode, oneShot, push, quit, refreshBalance, runCommand, settleLive, status.balance]);
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (phase !== 'ready' || !codeMode)
|
|
320
|
+
return;
|
|
321
|
+
const queued = queuedPromptRef.current;
|
|
322
|
+
if (!queued)
|
|
323
|
+
return;
|
|
324
|
+
queuedPromptRef.current = null;
|
|
325
|
+
void submit(queued);
|
|
326
|
+
}, [phase, codeMode, submit]);
|
|
284
327
|
useEffect(() => {
|
|
285
328
|
let cancelled = false;
|
|
286
329
|
(async () => {
|
|
@@ -392,20 +435,29 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
392
435
|
setPhase('question');
|
|
393
436
|
}),
|
|
394
437
|
}, session.mcp);
|
|
395
|
-
void session.mcpReady
|
|
438
|
+
void session.mcpReady
|
|
439
|
+
.then((reports) => {
|
|
396
440
|
if (cancelled || reports.length === 0)
|
|
397
441
|
return;
|
|
398
442
|
const good = reports.filter((r) => r.ok);
|
|
399
443
|
const tools = session.mcp.listTools().length;
|
|
400
|
-
if (good.length)
|
|
444
|
+
if (good.length) {
|
|
401
445
|
push('system', `MCP: ${good.length} server(s), ${tools} tool(s) available.`);
|
|
446
|
+
}
|
|
402
447
|
for (const bad of reports.filter((r) => !r.ok)) {
|
|
403
448
|
push('notice', `MCP server "${bad.name}" did not start: ${bad.error}`);
|
|
404
449
|
}
|
|
405
450
|
runnerRef.current?.setMcpTools(session.mcp.listTools());
|
|
451
|
+
})
|
|
452
|
+
.catch((err) => {
|
|
453
|
+
push('notice', `MCP servers could not be listed: ${err.message}`);
|
|
406
454
|
});
|
|
455
|
+
minCreditsRef.current = session.bootstrap.engine_min_credits ?? minCreditsRef.current;
|
|
407
456
|
sessionReadyRef.current = true;
|
|
408
|
-
setPhase(
|
|
457
|
+
setPhase((current) => phaseAfterBootstrap(current, {
|
|
458
|
+
sessionReady: true,
|
|
459
|
+
rateChosen: modeChosenRef.current,
|
|
460
|
+
}));
|
|
409
461
|
runnerRef.current?.setMode(mode);
|
|
410
462
|
push('system', `${session.project}${session.bootstrap.created ? ' · new session' : ''}`);
|
|
411
463
|
if (session.continuing) {
|
|
@@ -415,7 +467,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
415
467
|
push('notice', 'Project notes found in .rivine/local_memory.md.');
|
|
416
468
|
}
|
|
417
469
|
if (!oneShot && modeChosenRef.current && !askForMode) {
|
|
418
|
-
push('system', `
|
|
470
|
+
push('system', `Rate: ${describeMode(mode)}`);
|
|
419
471
|
push('system', 'Ask for anything. /help for commands.');
|
|
420
472
|
}
|
|
421
473
|
if (oneShot)
|
|
@@ -517,10 +569,17 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
|
|
|
517
569
|
const compactBanner = terminalWidth() < WORDMARK_MIN_COLUMNS || Boolean(oneShot);
|
|
518
570
|
const matches = completionsFor(input);
|
|
519
571
|
const busy = phase !== 'ready';
|
|
520
|
-
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 === 'codeMode' ? (_jsx(CodeModePicker, { onPick: pickCodeMode, current: codeModeChosenRef.current ? codeMode : undefined, onCancel: codeModeChosenRef.current ? cancelCodeModePick : undefined })) : null, phase === 'mode' ? (_jsx(
|
|
572
|
+
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 === 'codeMode' ? (_jsx(CodeModePicker, { onPick: pickCodeMode, current: codeModeChosenRef.current ? codeMode : undefined, onCancel: codeModeChosenRef.current ? cancelCodeModePick : undefined })) : null, phase === 'mode' ? (_jsx(RatePicker, { 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 ? (hitl.kind === 'code_mode' ? (_jsx(CodeModePicker, { onPick: (chosen) => {
|
|
573
|
+
setCodeMode(chosen);
|
|
574
|
+
codeModeChosenRef.current = true;
|
|
575
|
+
runnerRef.current?.setCodeMode(chosen);
|
|
576
|
+
push('system', `Mode: ${CODE_MODE_LABELS[chosen]}`);
|
|
577
|
+
answerHitl({ approved: true, feedback: CODE_MODE_LABELS[chosen], codeMode: chosen });
|
|
578
|
+
}, current: codeMode })) : (_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
|
|
521
579
|
? { approved: true, feedback: value }
|
|
522
|
-
: { 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: {
|
|
580
|
+
: { 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: {
|
|
523
581
|
mode,
|
|
582
|
+
codeMode,
|
|
524
583
|
project: status.project,
|
|
525
584
|
cloudhandle: status.cloudhandle,
|
|
526
585
|
bcidMasked: status.bcidMasked,
|
package/dist/ui/components.js
CHANGED
|
@@ -15,7 +15,7 @@ import { Spinner } from '@inkjs/ui';
|
|
|
15
15
|
import { ASCII_ONLY, BOX, color, glyph, lineColor } from './theme.js';
|
|
16
16
|
import { fit, terminalWidth, wrap, terminalColumns } from './render.js';
|
|
17
17
|
import { SLASH_COMMANDS } from './slashCommands.js';
|
|
18
|
-
import { describeMode, modeName } from '../protocol/permissions.js';
|
|
18
|
+
import { codeModeName, describeMode, modeName, } from '../protocol/permissions.js';
|
|
19
19
|
const WORDMARK = [
|
|
20
20
|
'█▀▄ ▀█▀ █ █ ▀█▀ █▄ █ █▀▀ █▀▀ █▀█ █▀▄ █▀▀',
|
|
21
21
|
'█▀▄ █ ▀▄ ▄▀ █ █ ▀█ █▀ █ █ █ █ █ █▀ ',
|
|
@@ -62,16 +62,16 @@ export function CodeModePicker({ onPick, current, onCancel, }) {
|
|
|
62
62
|
if (found)
|
|
63
63
|
onPick(found.mode);
|
|
64
64
|
});
|
|
65
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "Mode \u2014
|
|
65
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "Mode \u2014 what should Rivine Code work on?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((o) => {
|
|
66
66
|
const active = o.mode === current;
|
|
67
67
|
return (_jsxs(Box, { children: [_jsx(Text, { color: active ? color('success') : color('faint'), children: active ? ' \u203a' : ' ' }), _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 }), _jsx(Text, { color: color('faint'), children: " \u2014 " }), _jsx(Text, { color: color('faint'), children: fit(o.detail, Math.max(10, width - 34)) })] }, o.key));
|
|
68
68
|
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: color('faint'), children: current ? 'esc keeps the current mode' : 'pick a number to begin' }) })] }));
|
|
69
69
|
}
|
|
70
|
-
export function
|
|
70
|
+
export function RatePicker({ onPick, current, onCancel, }) {
|
|
71
71
|
const width = terminalWidth();
|
|
72
72
|
const options = [
|
|
73
|
-
{ key: '1', mode: 'default', label: 'Manual', detail: 'ask
|
|
74
|
-
{ key: '2', mode: 'bypass', label: 'Bypass', detail: 'edit
|
|
73
|
+
{ key: '1', mode: 'default', label: 'Manual', detail: 'ask before each edit' },
|
|
74
|
+
{ key: '2', mode: 'bypass', label: 'Bypass', detail: 'edit in this project without asking' },
|
|
75
75
|
];
|
|
76
76
|
useInput((input, key) => {
|
|
77
77
|
if (key.escape && onCancel) {
|
|
@@ -88,11 +88,10 @@ export function ModePicker({ onPick, current, onCancel, }) {
|
|
|
88
88
|
});
|
|
89
89
|
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: color('text'), bold: true, children: "Rate \u2014 how should Rivine apply edits?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((o) => {
|
|
90
90
|
const active = o.mode === current;
|
|
91
|
-
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(
|
|
91
|
+
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(8) }), _jsx(Text, { color: color('faint'), children: " \u2014 " }), _jsx(Text, { color: color('muted'), children: fit(o.detail, Math.max(10, width - 24)) })] }, o.key));
|
|
92
92
|
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: color('faint'), children: fit(current
|
|
93
|
-
?
|
|
94
|
-
|
|
95
|
-
: 'press a number · enter for accept edits · shift+tab changes it later', width - 4) }) })] }));
|
|
93
|
+
? `press a number · enter keeps ${options.find((o) => o.mode === current)?.label ?? 'this'} · esc cancels`
|
|
94
|
+
: 'press a number · enter for Manual · shift+tab toggles later', width - 4) }) })] }));
|
|
96
95
|
}
|
|
97
96
|
export const SHELL_PANE_LINES = 12;
|
|
98
97
|
export function ShellPane({ text, live }) {
|
|
@@ -137,7 +136,7 @@ export function Completions({ matches }) {
|
|
|
137
136
|
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [matches.slice(0, 6).map((c) => (_jsxs(Box, { children: [_jsx(Text, { color: color('accent'), children: c.name.padEnd(10) }), _jsx(Text, { color: color('faint'), children: fit(c.summary, width - 14) })] }, c.name))), matches.length === 1 ? (_jsx(Text, { color: color('faint'), children: " tab to complete" })) : null] }));
|
|
138
137
|
}
|
|
139
138
|
export function HelpPanel() {
|
|
140
|
-
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
|
|
139
|
+
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: " Mode what Rivine Code works on \u2014 /mode" }), _jsx(Text, { color: color('faint'), children: " Rate how edits apply \u2014 /rate, or shift+tab" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: color('faint'), children: " shift+tab toggle Rate (Manual \u21C4 Bypass)" }), _jsx(Text, { color: color('faint'), children: " esc stop the current turn" }), _jsx(Text, { color: color('faint'), children: " ctrl+c quit" })] })] }));
|
|
141
140
|
}
|
|
142
141
|
export function StatusBar({ info }) {
|
|
143
142
|
const width = terminalColumns();
|
|
@@ -157,18 +156,27 @@ export function StatusBar({ info }) {
|
|
|
157
156
|
segments.push({ node, text });
|
|
158
157
|
return true;
|
|
159
158
|
};
|
|
160
|
-
const
|
|
161
|
-
const
|
|
159
|
+
const rateText = describeMode(info.mode);
|
|
160
|
+
const rateShort = modeName(info.mode);
|
|
161
|
+
const agentText = info.codeMode ? codeModeName(info.codeMode) : '';
|
|
162
162
|
const brand = 'Rivine';
|
|
163
|
-
const
|
|
164
|
-
|
|
163
|
+
const fitsBrandAndFullRate = 2 +
|
|
164
|
+
brand.length +
|
|
165
|
+
(agentText ? 3 + agentText.length : 0) +
|
|
166
|
+
3 +
|
|
167
|
+
rateText.length <=
|
|
168
|
+
width - 2;
|
|
169
|
+
if (2 + brand.length + 3 + rateShort.length <= width - 2) {
|
|
165
170
|
add(brand, (_jsx(Text, { color: color('brand'), bold: true, children: brand })));
|
|
166
171
|
}
|
|
167
|
-
|
|
168
|
-
|
|
172
|
+
if (agentText) {
|
|
173
|
+
add(agentText, _jsx(Text, { color: color('accent'), children: agentText }));
|
|
174
|
+
}
|
|
175
|
+
const shownRate = fitsBrandAndFullRate ? rateText : rateShort;
|
|
176
|
+
used += (segments.length > 0 ? 3 : 0) + shownRate.length;
|
|
169
177
|
segments.push({
|
|
170
|
-
node: _jsx(Text, { color: color('muted'), children:
|
|
171
|
-
text:
|
|
178
|
+
node: _jsx(Text, { color: color('muted'), children: shownRate }),
|
|
179
|
+
text: shownRate,
|
|
172
180
|
});
|
|
173
181
|
if (info.balance !== null) {
|
|
174
182
|
const money = `$${info.balance.toFixed(2)}`;
|
package/dist/ui/headless.js
CHANGED
|
@@ -73,7 +73,7 @@ export class HeadlessRenderer {
|
|
|
73
73
|
refuseApproval(approval) {
|
|
74
74
|
const fix = this.mode === 'plan'
|
|
75
75
|
? 'plan mode makes no changes; re-run without --plan'
|
|
76
|
-
: 'no terminal to ask on — re-run with
|
|
76
|
+
: 'no terminal to ask on — re-run with --rate bypass';
|
|
77
77
|
this.refused += 1;
|
|
78
78
|
this.line('notice', `Skipped: ${approval.summary}`);
|
|
79
79
|
this.line('notice', ` ${approval.reason} (${fix})`);
|
|
@@ -85,7 +85,7 @@ export class HeadlessRenderer {
|
|
|
85
85
|
return '';
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
export function installTeardown(onExit) {
|
|
88
|
+
export function installTeardown(onExit, options = {}) {
|
|
89
89
|
let finished = false;
|
|
90
90
|
const restore = () => {
|
|
91
91
|
try {
|
|
@@ -116,15 +116,18 @@ export function installTeardown(onExit) {
|
|
|
116
116
|
process.stderr.write(`\n ${err?.message ?? String(err)}\n\n`);
|
|
117
117
|
void finish(1);
|
|
118
118
|
};
|
|
119
|
+
const onRejection = options.onRejection
|
|
120
|
+
? (err) => options.onRejection(err)
|
|
121
|
+
: onFatal;
|
|
119
122
|
process.on('SIGINT', onSigint);
|
|
120
123
|
process.on('SIGTERM', onSigterm);
|
|
121
124
|
process.on('uncaughtException', onFatal);
|
|
122
|
-
process.on('unhandledRejection',
|
|
125
|
+
process.on('unhandledRejection', onRejection);
|
|
123
126
|
return () => {
|
|
124
127
|
process.off('SIGINT', onSigint);
|
|
125
128
|
process.off('SIGTERM', onSigterm);
|
|
126
129
|
process.off('uncaughtException', onFatal);
|
|
127
|
-
process.off('unhandledRejection',
|
|
130
|
+
process.off('unhandledRejection', onRejection);
|
|
128
131
|
restore();
|
|
129
132
|
};
|
|
130
133
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Rivine Code CLI
|
|
3
|
+
* Licensed and owned by Beunec Technologies, Inc.
|
|
4
|
+
*
|
|
5
|
+
* https://aselius.beunec.com
|
|
6
|
+
*
|
|
7
|
+
* This software is proprietary. Use is governed by the licence agreement
|
|
8
|
+
* accompanying it; it is not open source and is not licensed for
|
|
9
|
+
* redistribution or derivative works.
|
|
10
|
+
*/
|
|
11
|
+
let sink = null;
|
|
12
|
+
export function setNoticeSink(next) {
|
|
13
|
+
sink = next;
|
|
14
|
+
}
|
|
15
|
+
export function emitNotice(text) {
|
|
16
|
+
if (!sink || !text)
|
|
17
|
+
return false;
|
|
18
|
+
sink(text);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
package/dist/ui/phase.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Rivine Code CLI
|
|
3
|
+
* Licensed and owned by Beunec Technologies, Inc.
|
|
4
|
+
*
|
|
5
|
+
* https://aselius.beunec.com
|
|
6
|
+
*
|
|
7
|
+
* This software is proprietary. Use is governed by the licence agreement
|
|
8
|
+
* accompanying it; it is not open source and is not licensed for
|
|
9
|
+
* redistribution or derivative works.
|
|
10
|
+
*/
|
|
11
|
+
export function phaseAfterBootstrap(current, s) {
|
|
12
|
+
if (current === 'codeMode' || current === 'mode')
|
|
13
|
+
return current;
|
|
14
|
+
return s.rateChosen ? 'ready' : 'mode';
|
|
15
|
+
}
|
|
16
|
+
export function phaseAfterModePicked(s) {
|
|
17
|
+
if (!s.rateChosen)
|
|
18
|
+
return 'mode';
|
|
19
|
+
return s.sessionReady ? 'ready' : 'starting';
|
|
20
|
+
}
|
|
21
|
+
export function phaseAfterRatePicked(s) {
|
|
22
|
+
return s.sessionReady ? 'ready' : 'starting';
|
|
23
|
+
}
|
|
24
|
+
export function initialPhase(hasMode, askRate) {
|
|
25
|
+
if (!hasMode)
|
|
26
|
+
return 'codeMode';
|
|
27
|
+
return askRate ? 'mode' : 'starting';
|
|
28
|
+
}
|
package/dist/ui/render.js
CHANGED
package/dist/ui/slashCommands.js
CHANGED
|
@@ -12,12 +12,25 @@ export const SLASH_COMMANDS = [
|
|
|
12
12
|
{ name: '/help', summary: 'show these commands' },
|
|
13
13
|
{
|
|
14
14
|
name: '/mode',
|
|
15
|
-
summary: '
|
|
16
|
-
detail: '
|
|
15
|
+
summary: 'pick the Mode — what Rivine Code works on',
|
|
16
|
+
detail: 'Frontend Only, Backend Only, Full Stack, Plan/Ask Only, or Security Scan/Fix.',
|
|
17
17
|
},
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
{
|
|
19
|
+
name: '/rate',
|
|
20
|
+
summary: 'pick the Rate — Manual or Bypass',
|
|
21
|
+
detail: 'Manual asks before each edit; Bypass applies them. Shift+Tab toggles.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: '/plan',
|
|
25
|
+
summary: 'switch to Plan/Ask Only Mode',
|
|
26
|
+
detail: 'Same as picking Plan/Ask Only in /mode.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: '/accept',
|
|
30
|
+
summary: 'switch Rate to Bypass',
|
|
31
|
+
detail: 'Accept-edits is gone; Bypass applies in-project edits without asking.',
|
|
32
|
+
},
|
|
33
|
+
{ name: '/status', summary: 'account, project, Mode, Rate and balance' },
|
|
21
34
|
{ name: '/diff', summary: 'what changed in the working tree' },
|
|
22
35
|
{
|
|
23
36
|
name: '/pr',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beunec/rivine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Rivine Code in your terminal
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"homepage": "https://aselius.beunec.com",
|