novaprime 1.6.2 → 1.7.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/bin/novaprime.js +6 -9
- package/package.json +1 -1
- package/src/agent.js +8 -2
- package/src/prompt.js +58 -0
- package/src/screen.js +39 -0
package/bin/novaprime.js
CHANGED
|
@@ -6,6 +6,7 @@ const { c } = ui;
|
|
|
6
6
|
const { ask, close, boxInput } = require('../src/prompt');
|
|
7
7
|
const { runTurn, fetchMe } = require('../src/agent');
|
|
8
8
|
const tools = require('../src/tools');
|
|
9
|
+
const screen = require('../src/screen');
|
|
9
10
|
const pkg = require('../package.json');
|
|
10
11
|
|
|
11
12
|
// Validate the key against the server before saving — a wrong key never logs in.
|
|
@@ -81,16 +82,12 @@ function showUsage(me) {
|
|
|
81
82
|
async function repl() {
|
|
82
83
|
const { cfg } = await ensureKey();
|
|
83
84
|
let me = await fetchMe(config.getServer(), cfg.key);
|
|
84
|
-
|
|
85
|
+
screen.enable(); // pinned-bottom chat layout: input stays at the bottom, chat scrolls above
|
|
85
86
|
ui.banner(meToBanner(cfg, me));
|
|
86
|
-
process.on('exit', () => { try { tools.stopBackground(); } catch (_) {} }); // stop bg dev servers on quit
|
|
87
|
-
process.on('SIGINT', () => { try { tools.stopBackground(); } catch (_) {} console.log(c.muted('\n bye')); process.exit(0); });
|
|
88
|
-
|
|
89
87
|
ui.hint(' Tip: press ' + c.indigo('Shift+Tab') + c.dim(' for auto-accept (no permission prompts), or answer ') + c.indigo('a') + c.dim(' at any prompt.'));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
process.stdout.write('\n'.repeat(Math.max(0, rows - 23)));
|
|
88
|
+
const quit = () => { try { tools.stopBackground(); } catch (_) {} try { screen.disable(); } catch (_) {} };
|
|
89
|
+
process.on('exit', quit);
|
|
90
|
+
process.on('SIGINT', () => { quit(); console.log(c.muted('\n bye')); process.exit(0); });
|
|
94
91
|
|
|
95
92
|
let messages = [];
|
|
96
93
|
while (true) {
|
|
@@ -103,7 +100,7 @@ async function repl() {
|
|
|
103
100
|
if (input === '/help') { printHelp(); continue; }
|
|
104
101
|
if (input === '/key') { console.log('\n ' + c.muted('key ') + c.green(ui.maskKey(cfg.key)) + '\n'); continue; }
|
|
105
102
|
if (input === '/usage') { me = await fetchMe(config.getServer(), cfg.key); showUsage(me); continue; }
|
|
106
|
-
if (input === '/clear') {
|
|
103
|
+
if (input === '/clear') { screen.enable(); me = await fetchMe(config.getServer(), cfg.key); ui.banner(meToBanner(cfg, me)); continue; }
|
|
107
104
|
|
|
108
105
|
ui.userBubble(input); // show the sent message as a clean bubble (box already erased)
|
|
109
106
|
messages.push({ role: 'user', content: input });
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -8,8 +8,14 @@ const { Renderer } = require('./render');
|
|
|
8
8
|
const SYSTEM_PROMPT =
|
|
9
9
|
`You are NovaPrime, a friendly AI coding assistant running inside the user's terminal. ` +
|
|
10
10
|
`You help with coding, files, databases (e.g. MySQL/XAMPP) and shell tasks. ` +
|
|
11
|
-
`You can read, write and edit files and run shell commands using the provided tools
|
|
12
|
-
`
|
|
11
|
+
`You can read, write and edit files and run shell commands using the provided tools. ` +
|
|
12
|
+
`IMPORTANT — full machine access: you can use ABSOLUTE paths to read/write/list files ANYWHERE on this PC, ` +
|
|
13
|
+
`including other drives like F:\\, D:\\, E:\\ — you are NOT limited to the current folder. ` +
|
|
14
|
+
`To FIND a file or folder anywhere, use run_command with a search command instead of guessing, e.g. on Windows: ` +
|
|
15
|
+
"`powershell -NoProfile -Command \"Get-ChildItem -Path F:\\,D:\\,C:\\Users -Recurse -Directory -Filter '*nova*' -ErrorAction SilentlyContinue | Select-Object -First 20 FullName\"` " +
|
|
16
|
+
`(or \`cmd /c dir /s /b \"F:\\*nova*\"\`). ` +
|
|
17
|
+
`NEVER refuse a request by claiming you can only access the current directory or cannot reach other drives — you can. ` +
|
|
18
|
+
`Always actually DO what the user asks using your tools; do not just describe limitations. ` +
|
|
13
19
|
`LANGUAGE: the user may write in English, Bangla (Bengali script), or romanized Banglish ` +
|
|
14
20
|
`(Bengali written with English letters, e.g. "tumi kemon acho", "kemon aso", "ki korso"). ` +
|
|
15
21
|
`Always understand them and reply in the SAME language and style the user used — if they write Banglish, reply in friendly Banglish. ` +
|
package/src/prompt.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const readline = require('readline');
|
|
3
3
|
const { c } = require('./ui');
|
|
4
4
|
const mode = require('./mode');
|
|
5
|
+
const screen = require('./screen');
|
|
5
6
|
|
|
6
7
|
// Use Node's readline for single-line prompts (login key, y/N). The multi-line
|
|
7
8
|
// chat box below is a custom raw-mode reader (paste-aware + Shift+Tab + full box).
|
|
@@ -111,6 +112,7 @@ function boxInput(top, bottom, prompt) {
|
|
|
111
112
|
r.question(prompt, (answer) => { process.stdout.write(bottom + '\n'); resolve(answer); });
|
|
112
113
|
});
|
|
113
114
|
}
|
|
115
|
+
if (screen.isOn()) return boxInputPinned(top, bottom, prompt); // pinned-bottom chat layout
|
|
114
116
|
if (rl) { rl.close(); rl = null; }
|
|
115
117
|
|
|
116
118
|
return new Promise((resolve) => {
|
|
@@ -168,4 +170,60 @@ function boxInput(top, bottom, prompt) {
|
|
|
168
170
|
});
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
// Pinned-bottom version: the box is drawn in the bottom rows (growing upward for
|
|
174
|
+
// multi-line), the conversation scrolls in the region above. On submit the box is
|
|
175
|
+
// reset to empty and the cursor parks in the region so the bubble/response flow above.
|
|
176
|
+
function boxInputPinned(top, bottom, prompt) {
|
|
177
|
+
if (rl) { rl.close(); rl = null; }
|
|
178
|
+
return new Promise((resolve) => {
|
|
179
|
+
const stdin = process.stdin;
|
|
180
|
+
const state = { buf: '', pasting: false, pending: '', finished: false };
|
|
181
|
+
const contPrefix = c.dim('│ ');
|
|
182
|
+
const statusLine = () => (mode.isAuto() ? [c.green(' ⚡ auto-accept ON') + c.dim(' · Shift+Tab to turn off')] : []);
|
|
183
|
+
|
|
184
|
+
function draw(inputLines) {
|
|
185
|
+
const status = statusLine();
|
|
186
|
+
const lines = [top, ...status, ...inputLines.map((ln, i) => (i === 0 ? prompt : contPrefix) + ln), bottom];
|
|
187
|
+
const boxH = lines.length;
|
|
188
|
+
const { rows } = screen.dims();
|
|
189
|
+
screen.setRegion(boxH);
|
|
190
|
+
const startRow = rows - boxH + 1;
|
|
191
|
+
let out = '';
|
|
192
|
+
for (let i = 0; i < lines.length; i++) out += '\x1b[' + (startRow + i) + ';1H\x1b[2K' + lines[i];
|
|
193
|
+
return { out, startRow, status, boxH };
|
|
194
|
+
}
|
|
195
|
+
function render() {
|
|
196
|
+
const inputLines = state.buf.split('\n');
|
|
197
|
+
const { out, startRow, status } = draw(inputLines);
|
|
198
|
+
const lastIdx = 1 + status.length + (inputLines.length - 1);
|
|
199
|
+
const prefVis = inputLines.length === 1 ? visLen(prompt) : visLen(contPrefix);
|
|
200
|
+
const col = prefVis + inputLines[inputLines.length - 1].length + 1;
|
|
201
|
+
process.stdout.write(out + '\x1b[' + (startRow + lastIdx) + ';' + col + 'H');
|
|
202
|
+
}
|
|
203
|
+
function cleanup() {
|
|
204
|
+
try { process.stdout.write('\x1b[?2004l'); } catch (_) {}
|
|
205
|
+
try { stdin.setRawMode(false); } catch (_) {}
|
|
206
|
+
stdin.pause();
|
|
207
|
+
stdin.removeListener('data', onData);
|
|
208
|
+
}
|
|
209
|
+
function finish(val) {
|
|
210
|
+
state.finished = true;
|
|
211
|
+
cleanup();
|
|
212
|
+
const { out, boxH } = draw(['']); // redraw an empty box (clears the typed text)
|
|
213
|
+
const { rows } = screen.dims();
|
|
214
|
+
process.stdout.write(out + '\x1b[' + (rows - boxH) + ';1H\n'); // park cursor in the region
|
|
215
|
+
resolve(val);
|
|
216
|
+
}
|
|
217
|
+
const sink = { submit: (v) => finish(v), cancel: () => finish(null), toggleMode: () => mode.toggle() };
|
|
218
|
+
const onData = (chunk) => { processChunk(state, chunk, sink); if (!state.finished) render(); };
|
|
219
|
+
|
|
220
|
+
process.stdout.write('\x1b[?2004h');
|
|
221
|
+
render();
|
|
222
|
+
stdin.setRawMode(true);
|
|
223
|
+
stdin.resume();
|
|
224
|
+
stdin.setEncoding('utf8');
|
|
225
|
+
stdin.on('data', onData);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
171
229
|
module.exports = { ask, confirm, boxInput, close, processChunk };
|
package/src/screen.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Pinned-bottom chat layout using a terminal scroll region (DECSTBM).
|
|
4
|
+
// The conversation lives in the scroll region (top area) and scrolls there,
|
|
5
|
+
// while the input box stays fixed in the bottom rows.
|
|
6
|
+
let on = false;
|
|
7
|
+
|
|
8
|
+
function dims() { return { rows: process.stdout.rows || 24, cols: process.stdout.columns || 80 }; }
|
|
9
|
+
|
|
10
|
+
function enable() {
|
|
11
|
+
if (!process.stdout.isTTY) return false;
|
|
12
|
+
on = true;
|
|
13
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H'); // clear screen + scrollback, cursor home
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function disable() {
|
|
18
|
+
if (!on) return;
|
|
19
|
+
on = false;
|
|
20
|
+
const { rows } = dims();
|
|
21
|
+
process.stdout.write('\x1b[r'); // reset scroll region to full screen
|
|
22
|
+
process.stdout.write('\x1b[' + rows + ';1H\n'); // park cursor at the bottom
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Scroll region = rows 1..(rows-boxH); the bottom boxH rows hold the input box.
|
|
26
|
+
function setRegion(boxH) {
|
|
27
|
+
const { rows } = dims();
|
|
28
|
+
const bottom = Math.max(1, rows - boxH);
|
|
29
|
+
process.stdout.write('\x1b[1;' + bottom + 'r');
|
|
30
|
+
return bottom;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Put the cursor at the last line of the conversation region (so prints scroll there).
|
|
34
|
+
function toRegionBottom(boxH) {
|
|
35
|
+
const { rows } = dims();
|
|
36
|
+
process.stdout.write('\x1b[' + Math.max(1, rows - boxH) + ';1H');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { enable, disable, isOn: () => on, dims, setRegion, toRegionBottom };
|