@beunec/rivine 0.2.1 → 0.3.1

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/ui/App.js CHANGED
@@ -22,8 +22,10 @@ import { sanitizeForModel } from '../protocol/shell.js';
22
22
  import { diff as gitDiff, gitBranchLine, prCreate } from '../protocol/git.js';
23
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,9 +42,10 @@ 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 ? (askForMode ? 'mode' : 'starting') : 'codeMode');
45
+ const [phase, setPhase] = useState(initialPhase(Boolean(initialCodeMode), Boolean(askForMode)));
44
46
  const sessionReadyRef = useRef(false);
45
47
  const modeChosenRef = useRef(!askForMode);
48
+ const invitedRef = useRef(false);
46
49
  const [lines, setLines] = useState([]);
47
50
  const [live, setLive] = useState('');
48
51
  const [shellView, setShellView] = useState('');
@@ -74,6 +77,8 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
74
77
  const mcpRef = useRef(null);
75
78
  const snapshotRef = useRef(null);
76
79
  const snapshotSentRef = useRef(false);
80
+ const minCreditsRef = useRef({ rivine_code: 10 });
81
+ const queuedPromptRef = useRef(null);
77
82
  const answerRef = useRef(null);
78
83
  const lineId = useRef(0);
79
84
  const liveText = useRef('');
@@ -94,6 +99,12 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
94
99
  const id = lineId.current;
95
100
  setLines((prev) => [...prev, { id, kind, text, detail }]);
96
101
  }, []);
102
+ const invite = useCallback(() => {
103
+ if (invitedRef.current)
104
+ return;
105
+ invitedRef.current = true;
106
+ push('system', 'Ask for anything. /help for commands.');
107
+ }, [push]);
97
108
  const settleLive = useCallback(() => {
98
109
  tokens.flush();
99
110
  const text = liveText.current.trim();
@@ -119,9 +130,10 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
119
130
  codeModeChosenRef.current = true;
120
131
  runnerRef.current?.setCodeMode(chosen);
121
132
  push('system', `Mode: ${CODE_MODE_LABELS[chosen]}`);
122
- setPhase(modeChosenRef.current
123
- ? (sessionReadyRef.current ? 'ready' : 'starting')
124
- : 'mode');
133
+ setPhase(phaseAfterModePicked({
134
+ sessionReady: sessionReadyRef.current,
135
+ rateChosen: modeChosenRef.current,
136
+ }));
125
137
  }, [push]);
126
138
  const cancelCodeModePick = useCallback(() => {
127
139
  setPhase(sessionReadyRef.current ? 'ready' : 'starting');
@@ -130,18 +142,29 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
130
142
  setMode(chosen);
131
143
  modeChosenRef.current = true;
132
144
  runnerRef.current?.setMode(chosen);
133
- setPhase(sessionReadyRef.current ? 'ready' : 'starting');
145
+ setPhase(phaseAfterRatePicked({
146
+ sessionReady: sessionReadyRef.current,
147
+ rateChosen: true,
148
+ }));
134
149
  push('system', `Rate: ${describeMode(chosen)}`);
135
150
  if (chosen === 'bypass') {
136
151
  push('notice', 'Running without prompts. Files outside this project are still refused.');
137
152
  }
138
- push('system', 'Ask for anything. /help for commands.');
139
- }, [push]);
153
+ invite();
154
+ }, [push, invite]);
140
155
  const cancelModePick = useCallback(() => {
141
156
  setPhase(sessionReadyRef.current ? 'ready' : 'starting');
142
157
  }, []);
158
+ useEffect(() => {
159
+ setNoticeSink((text) => push('notice', text));
160
+ return () => setNoticeSink(null);
161
+ }, [push]);
143
162
  const quit = useCallback(() => {
144
- void runnerRef.current?.shutdown().finally(() => exit());
163
+ void runnerRef.current
164
+ ?.shutdown()
165
+ .catch(() => {
166
+ })
167
+ .finally(() => exit());
145
168
  }, [exit]);
146
169
  const cycleMode = useCallback(() => {
147
170
  setMode((current) => {
@@ -168,10 +191,10 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
168
191
  setShowHelp(false);
169
192
  setScreenGeneration((n) => n + 1);
170
193
  return true;
171
- case '/agent':
194
+ case '/mode':
172
195
  setPhase('codeMode');
173
196
  return true;
174
- case '/mode':
197
+ case '/rate':
175
198
  setPhase('mode');
176
199
  return true;
177
200
  case '/plan':
@@ -179,6 +202,10 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
179
202
  codeModeChosenRef.current = true;
180
203
  runnerRef.current?.setCodeMode('simple');
181
204
  push('system', `Mode: ${CODE_MODE_LABELS.simple}`);
205
+ setPhase(phaseAfterModePicked({
206
+ sessionReady: sessionReadyRef.current,
207
+ rateChosen: modeChosenRef.current,
208
+ }));
182
209
  return true;
183
210
  case '/accept':
184
211
  setMode('bypass');
@@ -249,7 +276,13 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
249
276
  return;
250
277
  if (runCommand(text))
251
278
  return;
252
- const needed = creditShortfall(status.balance, { rivine_code: 10 });
279
+ if (!codeMode) {
280
+ queuedPromptRef.current = text;
281
+ push('notice', 'Pick a Mode first — it decides what Rivine Code works on. Your message is saved.');
282
+ setPhase('codeMode');
283
+ return;
284
+ }
285
+ const needed = creditShortfall(status.balance, minCreditsRef.current);
253
286
  if (needed !== null) {
254
287
  push('error', `Rivine Code needs $${needed.toFixed(2)} in credits to run.`);
255
288
  push('notice', `Top up at ${siteUrl()}/credits`);
@@ -265,8 +298,12 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
265
298
  try {
266
299
  const result = await runnerRef.current.run(text, snapshot);
267
300
  settleLive();
268
- if (result.error)
301
+ if (result.error) {
269
302
  push('error', result.error);
303
+ if (/credit|insufficient/i.test(result.error)) {
304
+ push('notice', `Top up at ${siteUrl()}/credits`);
305
+ }
306
+ }
270
307
  else if (result.answer)
271
308
  push('agent', result.answer);
272
309
  }
@@ -288,7 +325,16 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
288
325
  if (oneShot)
289
326
  quit();
290
327
  }
291
- }, [oneShot, push, quit, refreshBalance, runCommand, settleLive, status.balance]);
328
+ }, [codeMode, oneShot, push, quit, refreshBalance, runCommand, settleLive, status.balance]);
329
+ useEffect(() => {
330
+ if (phase !== 'ready' || !codeMode)
331
+ return;
332
+ const queued = queuedPromptRef.current;
333
+ if (!queued)
334
+ return;
335
+ queuedPromptRef.current = null;
336
+ void submit(queued);
337
+ }, [phase, codeMode, submit]);
292
338
  useEffect(() => {
293
339
  let cancelled = false;
294
340
  (async () => {
@@ -400,20 +446,29 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
400
446
  setPhase('question');
401
447
  }),
402
448
  }, session.mcp);
403
- void session.mcpReady.then((reports) => {
449
+ void session.mcpReady
450
+ .then((reports) => {
404
451
  if (cancelled || reports.length === 0)
405
452
  return;
406
453
  const good = reports.filter((r) => r.ok);
407
454
  const tools = session.mcp.listTools().length;
408
- if (good.length)
455
+ if (good.length) {
409
456
  push('system', `MCP: ${good.length} server(s), ${tools} tool(s) available.`);
457
+ }
410
458
  for (const bad of reports.filter((r) => !r.ok)) {
411
459
  push('notice', `MCP server "${bad.name}" did not start: ${bad.error}`);
412
460
  }
413
461
  runnerRef.current?.setMcpTools(session.mcp.listTools());
462
+ })
463
+ .catch((err) => {
464
+ push('notice', `MCP servers could not be listed: ${err.message}`);
414
465
  });
466
+ minCreditsRef.current = session.bootstrap.engine_min_credits ?? minCreditsRef.current;
415
467
  sessionReadyRef.current = true;
416
- setPhase(modeChosenRef.current ? 'ready' : 'mode');
468
+ setPhase((current) => phaseAfterBootstrap(current, {
469
+ sessionReady: true,
470
+ rateChosen: modeChosenRef.current,
471
+ }));
417
472
  runnerRef.current?.setMode(mode);
418
473
  push('system', `${session.project}${session.bootstrap.created ? ' · new session' : ''}`);
419
474
  if (session.continuing) {
@@ -424,7 +479,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
424
479
  }
425
480
  if (!oneShot && modeChosenRef.current && !askForMode) {
426
481
  push('system', `Rate: ${describeMode(mode)}`);
427
- push('system', 'Ask for anything. /help for commands.');
482
+ invite();
428
483
  }
429
484
  if (oneShot)
430
485
  void submit(oneShot);
@@ -525,9 +580,15 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
525
580
  const compactBanner = terminalWidth() < WORDMARK_MIN_COLUMNS || Boolean(oneShot);
526
581
  const matches = completionsFor(input);
527
582
  const busy = phase !== 'ready';
528
- 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 ? (_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
583
+ 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) => {
584
+ setCodeMode(chosen);
585
+ codeModeChosenRef.current = true;
586
+ runnerRef.current?.setCodeMode(chosen);
587
+ push('system', `Mode: ${CODE_MODE_LABELS[chosen]}`);
588
+ answerHitl({ approved: true, feedback: CODE_MODE_LABELS[chosen], codeMode: chosen });
589
+ }, 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
529
590
  ? { approved: true, feedback: value }
530
- : { 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: {
591
+ : { 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: {
531
592
  mode,
532
593
  codeMode,
533
594
  project: status.project,
@@ -62,7 +62,7 @@ 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 which agent should handle this?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((o) => {
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' }) })] }));
@@ -93,7 +93,6 @@ export function RatePicker({ onPick, current, onCancel, }) {
93
93
  ? `press a number · enter keeps ${options.find((o) => o.mode === current)?.label ?? 'this'} · esc cancels`
94
94
  : 'press a number · enter for Manual · shift+tab toggles later', width - 4) }) })] }));
95
95
  }
96
- export const ModePicker = RatePicker;
97
96
  export const SHELL_PANE_LINES = 12;
98
97
  export function ShellPane({ text, live }) {
99
98
  if (!text)
@@ -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 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" })] })] }));
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();
@@ -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 -m acceptEdits, or --bypass in CI';
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', onFatal);
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', onFatal);
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
+ }
@@ -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
@@ -111,3 +111,6 @@ export function tail(text, max) {
111
111
  return { lines, hidden: 0 };
112
112
  return { lines: lines.slice(-max), hidden: lines.length - max };
113
113
  }
114
+ export function displayStage(label) {
115
+ return /^routed to\b/i.test(label.trim()) ? '' : label;
116
+ }
@@ -11,19 +11,19 @@
11
11
  export const SLASH_COMMANDS = [
12
12
  { name: '/help', summary: 'show these commands' },
13
13
  {
14
- name: '/agent',
15
- summary: 'pick which engine Mode runs',
16
- detail: 'Frontend, Backend, Full Stack, Plan/Ask, or Security.',
14
+ name: '/mode',
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
18
  {
19
- name: '/mode',
19
+ name: '/rate',
20
20
  summary: 'pick the Rate — Manual or Bypass',
21
- detail: 'Shift+Tab toggles Manual ⇄ Bypass.',
21
+ detail: 'Manual asks before each edit; Bypass applies them. Shift+Tab toggles.',
22
22
  },
23
23
  {
24
24
  name: '/plan',
25
25
  summary: 'switch to Plan/Ask Only Mode',
26
- detail: 'Same as picking Plan/Ask in /agent.',
26
+ detail: 'Same as picking Plan/Ask Only in /mode.',
27
27
  },
28
28
  {
29
29
  name: '/accept',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beunec/rivine",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",