@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/README.md +69 -68
- package/dist/api/workspace.js +44 -4
- package/dist/index.js +6 -2
- package/dist/protocol/fsExecutor.js +36 -2
- package/dist/protocol/permissions.js +104 -0
- package/dist/protocol/runner.js +280 -85
- package/dist/protocol/shell.js +5 -2
- package/dist/runHeadless.js +6 -0
- package/dist/session.js +69 -3
- package/dist/ui/App.js +24 -4
- package/dist/ui/headless.js +5 -1
- package/dist/ui/slashCommands.js +5 -0
- package/package.json +1 -1
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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' });
|
package/dist/ui/headless.js
CHANGED
|
@@ -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
|
-
:
|
|
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})`);
|
package/dist/ui/slashCommands.js
CHANGED
|
@@ -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.
|
|
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",
|