novaprime 1.2.6 → 1.2.8

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
@@ -3,7 +3,7 @@
3
3
  const config = require('../src/config');
4
4
  const ui = require('../src/ui');
5
5
  const { c } = ui;
6
- const { ask, close, get } = require('../src/prompt');
6
+ const { ask, close, boxInput } = require('../src/prompt');
7
7
  const { runTurn, fetchMe } = require('../src/agent');
8
8
  const pkg = require('../package.json');
9
9
 
@@ -78,18 +78,15 @@ async function repl() {
78
78
  let me = await fetchMe(config.getServer(), cfg.key);
79
79
  console.clear(); // hide login/clutter — start clean with the header at the top
80
80
  ui.banner(meToBanner(cfg, me));
81
- get().on('SIGINT', () => { console.log(c.muted('\n bye')); close(); process.exit(0); });
81
+ process.on('SIGINT', () => { console.log(c.muted('\n bye')); process.exit(0); });
82
82
 
83
83
  // push the first input box toward the lower part of the screen, leaving a bottom margin
84
84
  const rows = process.stdout.rows || 24;
85
- process.stdout.write('\n'.repeat(Math.max(0, rows - 29)));
85
+ process.stdout.write('\n'.repeat(Math.max(0, rows - 27)));
86
86
 
87
87
  let messages = [];
88
88
  while (true) {
89
- console.log('');
90
- ui.inputBoxOpen();
91
- const raw = await ask(ui.inputPrompt());
92
- ui.inputBoxClose();
89
+ const raw = await boxInput(ui.inputTop(), ui.inputBottom(), ui.inputPrompt());
93
90
 
94
91
  if (raw === null) { console.log(c.muted(' bye')); break; }
95
92
  const input = raw.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novaprime",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
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/prompt.js CHANGED
@@ -1,27 +1,66 @@
1
1
  'use strict';
2
- const readline = require('readline');
2
+ const ESC = String.fromCharCode(27);
3
3
 
4
- let rl = null;
5
- function get() {
6
- if (!rl) rl = readline.createInterface({ input: process.stdin, output: process.stdout, historySize: 200 });
7
- return rl;
8
- }
9
-
10
- // ask a free-text question, returns the typed line (or null on Ctrl+C/EOF)
11
- function ask(promptStr) {
4
+ // Raw-mode line reader. Draws optional frame, edits a single line in place,
5
+ // and (unlike readline) never clears the lines below — so a box border stays visible.
6
+ function readRaw(prompt, opts = {}) {
12
7
  return new Promise((resolve) => {
13
- const r = get();
14
- const onClose = () => resolve(null);
15
- r.once('close', onClose);
16
- r.question(promptStr, (answer) => { r.removeListener('close', onClose); resolve(answer); });
8
+ const stdin = process.stdin, stdout = process.stdout;
9
+
10
+ // Non-TTY fallback (piped / one-shot): just read a line.
11
+ if (!stdin.isTTY) {
12
+ stdin.resume(); stdin.setEncoding('utf8');
13
+ let acc = '';
14
+ const onData = (d) => {
15
+ acc += d;
16
+ const nl = acc.indexOf('\n');
17
+ if (nl >= 0) { stdin.removeListener('data', onData); stdin.pause(); resolve(acc.slice(0, nl).replace(/\r$/, '')); }
18
+ };
19
+ stdin.on('data', onData);
20
+ return;
21
+ }
22
+
23
+ let buf = '';
24
+ if (opts.draw) opts.draw();
25
+ stdin.setRawMode(true); stdin.resume(); stdin.setEncoding('utf8');
26
+ const redraw = () => stdout.write('\r' + ESC + '[2K' + prompt + buf);
27
+ redraw();
28
+
29
+ const done = (val) => {
30
+ stdin.setRawMode(false); stdin.pause(); stdin.removeListener('data', onData);
31
+ if (opts.after) opts.after();
32
+ resolve(val);
33
+ };
34
+ function onData(s) {
35
+ let i = 0;
36
+ while (i < s.length) {
37
+ const ch = s[i], code = s.charCodeAt(i);
38
+ if (ch === '\r' || ch === '\n') { return done(buf); }
39
+ if (code === 3) { stdin.setRawMode(false); stdout.write('\n'); process.exit(0); } // Ctrl+C
40
+ if (code === 4) { return done(buf.length ? buf : null); } // Ctrl+D
41
+ if (code === 127 || code === 8) { buf = buf.slice(0, -1); redraw(); i++; continue; } // backspace
42
+ if (code === 27) { i += (s[i + 1] === '[') ? 3 : 1; continue; } // skip arrow/escape seq
43
+ if (code < 32) { i++; continue; }
44
+ buf += ch; redraw(); i++;
45
+ }
46
+ }
47
+ stdin.on('data', onData);
17
48
  });
18
49
  }
19
50
 
20
- // yes/no confirmation (default No)
21
- function confirm(message) {
22
- return ask(message + ' (y/N) ').then((a) => /^y(es)?$/i.test((a || '').trim()));
51
+ function ask(prompt) { return readRaw(prompt || ''); }
52
+ function confirm(message) { return readRaw(message + ' (y/N) ').then((a) => /^y(es)?$/i.test((a || '').trim())); }
53
+
54
+ // Full chat box that stays drawn while typing (top + input line + bottom).
55
+ function boxInput(top, bottom, prompt) {
56
+ return readRaw(prompt, {
57
+ draw: () => process.stdout.write('\n' + top + '\n\n' + bottom + '\n' + ESC + '[2A'),
58
+ after: () => process.stdout.write('\r\n\n'),
59
+ });
23
60
  }
24
61
 
25
- function close() { if (rl) { rl.close(); rl = null; } }
62
+ function close() {
63
+ try { if (process.stdin.isTTY) process.stdin.setRawMode(false); process.stdin.pause(); } catch (_) {}
64
+ }
26
65
 
27
- module.exports = { ask, confirm, close, get };
66
+ module.exports = { ask, confirm, boxInput, close };