novaprime 1.2.5 → 1.2.7
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 +5 -8
- package/package.json +1 -1
- package/src/prompt.js +57 -18
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,
|
|
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
|
-
|
|
81
|
+
process.on('SIGINT', () => { console.log(c.muted('\n bye')); process.exit(0); });
|
|
82
82
|
|
|
83
|
-
// push the first input box toward the
|
|
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 -
|
|
85
|
+
process.stdout.write('\n'.repeat(Math.max(0, rows - 29)));
|
|
86
86
|
|
|
87
87
|
let messages = [];
|
|
88
88
|
while (true) {
|
|
89
|
-
|
|
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
package/src/prompt.js
CHANGED
|
@@ -1,27 +1,66 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const
|
|
2
|
+
const ESC = String.fromCharCode(27);
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
function confirm(message) {
|
|
22
|
-
|
|
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() {
|
|
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,
|
|
66
|
+
module.exports = { ask, confirm, boxInput, close };
|