@beunec/rivine 0.3.0 → 0.3.2

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/session.js CHANGED
@@ -8,12 +8,25 @@
8
8
  * accompanying it; it is not open source and is not licensed for
9
9
  * redistribution or derivative works.
10
10
  */
11
+ import { readFileSync } from 'node:fs';
11
12
  import { stat } from 'node:fs/promises';
12
- import { basename, join } from 'node:path';
13
- import { bootstrap, fetchCredits } from './api/workspace.js';
13
+ import { basename, dirname, join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { bootstrap, fetchCredits, OnboardingRequired } from './api/workspace.js';
16
+ import { openBrowser } from './auth/pkce.js';
14
17
  import { isSignedIn, login } from './auth/session.js';
15
18
  import { McpManager } from './mcp/manager.js';
16
19
  import { buildSnapshot } from './protocol/snapshot.js';
20
+ export function packageVersion() {
21
+ try {
22
+ const here = dirname(fileURLToPath(import.meta.url));
23
+ const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
24
+ return String(pkg.version ?? '0.0.0');
25
+ }
26
+ catch {
27
+ return '0.0.0';
28
+ }
29
+ }
17
30
  async function localMemoryPresent(cwd) {
18
31
  try {
19
32
  const info = await stat(join(cwd, '.rivine', 'local_memory.md'));
@@ -23,6 +36,30 @@ async function localMemoryPresent(cwd) {
23
36
  return false;
24
37
  }
25
38
  }
39
+ const ONBOARDING_POLL_MS = 5_000;
40
+ const ONBOARDING_WAIT_MS = 20 * 60_000;
41
+ async function bootstrapAfterOnboarding(cwd, project, options) {
42
+ let waitingSince = 0;
43
+ for (;;) {
44
+ try {
45
+ return await bootstrap(cwd, project);
46
+ }
47
+ catch (err) {
48
+ if (!(err instanceof OnboardingRequired))
49
+ throw err;
50
+ if (!waitingSince) {
51
+ waitingSince = Date.now();
52
+ options.onNotice?.(`${err.message} Opening ${err.url} — choose your cloudhandle there, then come back. Rivine Code continues by itself.`);
53
+ options.onLoginUrl?.(err.url);
54
+ openBrowser(err.url);
55
+ }
56
+ else if (Date.now() - waitingSince > ONBOARDING_WAIT_MS) {
57
+ throw new Error(`Your Aselius account is not set up yet. Finish it at ${err.url}, then run \`rivine\` again.`);
58
+ }
59
+ await new Promise((r) => setTimeout(r, ONBOARDING_POLL_MS));
60
+ }
61
+ }
62
+ }
26
63
  export async function startSession(options) {
27
64
  const { cwd } = options;
28
65
  if (!(await isSignedIn())) {
@@ -33,7 +70,10 @@ export async function startSession(options) {
33
70
  });
34
71
  }
35
72
  const project = basename(cwd) || 'project';
36
- const result = await bootstrap(cwd, project);
73
+ const result = await bootstrapAfterOnboarding(cwd, project, options);
74
+ for (const notice of continuationNotices(result, packageVersion())) {
75
+ options.onNotice?.(notice);
76
+ }
37
77
  const mcp = new McpManager();
38
78
  const mcpReady = mcp.connectAll(cwd).catch(() => []);
39
79
  return {
@@ -55,3 +95,29 @@ export function creditShortfall(balance, minimums) {
55
95
  return null;
56
96
  return balance < floor ? floor : null;
57
97
  }
98
+ export function compareSemver(a, b) {
99
+ const parts = (v) => String(v || '0')
100
+ .replace(/^v/i, '')
101
+ .split(/[.+-]/)
102
+ .map((p) => parseInt(p, 10) || 0);
103
+ const aa = parts(a);
104
+ const bb = parts(b);
105
+ const n = Math.max(aa.length, bb.length);
106
+ for (let i = 0; i < n; i += 1) {
107
+ const d = (aa[i] ?? 0) - (bb[i] ?? 0);
108
+ if (d)
109
+ return d < 0 ? -1 : 1;
110
+ }
111
+ return 0;
112
+ }
113
+ export function continuationNotices(bootstrap, localVersion) {
114
+ const out = [];
115
+ const min = bootstrap.min_cli_version;
116
+ if (min && compareSemver(localVersion, min) < 0) {
117
+ out.push(`This CLI is ${localVersion}; long turns need >= ${min}. Upgrade: npm install -g @beunec/rivine@latest`);
118
+ }
119
+ if (bootstrap.continuation_enabled === false) {
120
+ out.push('This Workspace has turn continuation off — long Rivine Code builds may stop at the server time limit.');
121
+ }
122
+ return out;
123
+ }
package/dist/ui/App.js CHANGED
@@ -45,6 +45,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
45
45
  const [phase, setPhase] = useState(initialPhase(Boolean(initialCodeMode), Boolean(askForMode)));
46
46
  const sessionReadyRef = useRef(false);
47
47
  const modeChosenRef = useRef(!askForMode);
48
+ const invitedRef = useRef(false);
48
49
  const [lines, setLines] = useState([]);
49
50
  const [live, setLive] = useState('');
50
51
  const [shellView, setShellView] = useState('');
@@ -98,6 +99,12 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
98
99
  const id = lineId.current;
99
100
  setLines((prev) => [...prev, { id, kind, text, detail }]);
100
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]);
101
108
  const settleLive = useCallback(() => {
102
109
  tokens.flush();
103
110
  const text = liveText.current.trim();
@@ -143,8 +150,8 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
143
150
  if (chosen === 'bypass') {
144
151
  push('notice', 'Running without prompts. Files outside this project are still refused.');
145
152
  }
146
- push('system', 'Ask for anything. /help for commands.');
147
- }, [push]);
153
+ invite();
154
+ }, [push, invite]);
148
155
  const cancelModePick = useCallback(() => {
149
156
  setPhase(sessionReadyRef.current ? 'ready' : 'starting');
150
157
  }, []);
@@ -206,6 +213,10 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
206
213
  push('system', `Rate: ${describeMode('bypass')}`);
207
214
  push('notice', 'Accept-edits is gone — Bypass applies in-project edits without asking.');
208
215
  return true;
216
+ case '/stop':
217
+ push('system', 'Stopping any turn still running on the server…');
218
+ void runnerRef.current?.stop().then(() => push('system', 'Stopped.'));
219
+ return true;
209
220
  case '/credits':
210
221
  openBrowser(`${siteUrl()}/credits`);
211
222
  push('system', `Opened ${siteUrl()}/credits`);
@@ -291,8 +302,12 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
291
302
  try {
292
303
  const result = await runnerRef.current.run(text, snapshot);
293
304
  settleLive();
294
- if (result.error)
305
+ if (result.error) {
295
306
  push('error', result.error);
307
+ if (/credit|insufficient/i.test(result.error)) {
308
+ push('notice', `Top up at ${siteUrl()}/credits`);
309
+ }
310
+ }
296
311
  else if (result.answer)
297
312
  push('agent', result.answer);
298
313
  }
@@ -468,7 +483,7 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
468
483
  }
469
484
  if (!oneShot && modeChosenRef.current && !askForMode) {
470
485
  push('system', `Rate: ${describeMode(mode)}`);
471
- push('system', 'Ask for anything. /help for commands.');
486
+ invite();
472
487
  }
473
488
  if (oneShot)
474
489
  void submit(oneShot);
@@ -525,6 +540,11 @@ export function App({ cwd, initialMode, askForMode, initialCodeMode, oneShot })
525
540
  push('notice', 'Stopped the running command.');
526
541
  return;
527
542
  }
543
+ if (key.escape && phase === 'running') {
544
+ push('notice', 'Stopping this turn…');
545
+ void runnerRef.current?.stop();
546
+ return;
547
+ }
528
548
  if (key.ctrl && ch === 'c')
529
549
  quit();
530
550
  }, { isActive: phase === 'ready' || phase === 'running' });
@@ -73,7 +73,11 @@ 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 --rate bypass';
76
+ : approval.category === 'destructive'
77
+ ? 'cannot be undone and there is no terminal to confirm on — run it yourself, or set RIVINE_ALLOW_DESTRUCTIVE=1'
78
+ : approval.category === 'secret'
79
+ ? 'would send credentials to the model and there is no terminal to confirm on'
80
+ : 'no terminal to ask on — re-run with --rate bypass';
77
81
  this.refused += 1;
78
82
  this.line('notice', `Skipped: ${approval.summary}`);
79
83
  this.line('notice', ` ${approval.reason} (${fix})`);
@@ -30,6 +30,11 @@ export const SLASH_COMMANDS = [
30
30
  summary: 'switch Rate to Bypass',
31
31
  detail: 'Accept-edits is gone; Bypass applies in-project edits without asking.',
32
32
  },
33
+ {
34
+ name: '/stop',
35
+ summary: 'stop a turn still running on the server',
36
+ detail: 'While a turn runs, Esc does the same (when no command is running; Esc first stops the command).',
37
+ },
33
38
  { name: '/status', summary: 'account, project, Mode, Rate and balance' },
34
39
  { name: '/diff', summary: 'what changed in the working tree' },
35
40
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beunec/rivine",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",