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 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
- console.clear(); // hide login/clutter start clean with the header at the top
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
- // push the first input box toward the lower part of the screen (a bit lower than before)
92
- const rows = process.stdout.rows || 24;
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') { console.clear(); me = await fetchMe(config.getServer(), cfg.key); ui.banner(meToBanner(cfg, me)); continue; }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novaprime",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "description": "NovaPrime — an AI coding assistant in your terminal, powered by GLM.",
5
5
  "bin": {
6
6
  "novaprime": "bin/novaprime.js"
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
- `all relative to the user's current working directory. ` +
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 };