novac 2.0.1 → 2.2.0
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/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
package/src/core/bstd.js
CHANGED
|
@@ -2,6 +2,9 @@ const { CustomError, formatError, NovaException } = require('./error.js');
|
|
|
2
2
|
const { hash } = require('crypto');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { execFileSync, execSync } = require('child_process');
|
|
5
8
|
|
|
6
9
|
let Air = {};
|
|
7
10
|
|
|
@@ -12,15 +15,20 @@ fs.readdirSync(__dirname)
|
|
|
12
15
|
const name = path.basename(file, '.js');
|
|
13
16
|
Air[name] = require(path.join(__dirname, file));
|
|
14
17
|
});
|
|
18
|
+
|
|
15
19
|
let Chalk = require('chalk').default;
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// History (unchanged from original)
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
24
|
let History = {
|
|
17
|
-
getKey(name) {return
|
|
18
|
-
exists(key) { return fs.existsSync(key) },
|
|
25
|
+
getKey(name) { return '.184.' + name; },
|
|
26
|
+
exists(key) { return fs.existsSync(key); },
|
|
19
27
|
createHistory(key) {
|
|
20
|
-
fs.writeFileSync(key, JSON.stringify({history:[]}));
|
|
28
|
+
fs.writeFileSync(key, JSON.stringify({ history: [] }));
|
|
21
29
|
return key;
|
|
22
30
|
},
|
|
23
|
-
appendHistory(key,addition, pushOrIndex, index) {
|
|
31
|
+
appendHistory(key, addition, pushOrIndex, index) {
|
|
24
32
|
let content = JSON.parse(fs.readFileSync(key));
|
|
25
33
|
if (pushOrIndex === 'index') content.history[index] = addition;
|
|
26
34
|
else content.history.push(addition);
|
|
@@ -28,7 +36,7 @@ let History = {
|
|
|
28
36
|
},
|
|
29
37
|
readHistory(key) {
|
|
30
38
|
let content = JSON.parse(fs.readFileSync(key));
|
|
31
|
-
|
|
39
|
+
return content.history;
|
|
32
40
|
},
|
|
33
41
|
rawSetHistory(key, historyArray) {
|
|
34
42
|
let content = JSON.parse(fs.readFileSync(key));
|
|
@@ -40,194 +48,31 @@ let History = {
|
|
|
40
48
|
return content.history;
|
|
41
49
|
},
|
|
42
50
|
clearHistory(key) {
|
|
43
|
-
fs.writeFileSync(key, JSON.stringify({history:[]}));
|
|
51
|
+
fs.writeFileSync(key, JSON.stringify({ history: [] }));
|
|
44
52
|
}
|
|
45
|
-
}
|
|
46
|
-
let Input = {
|
|
47
|
-
prompt(question, usesHistory = false, options) {
|
|
48
|
-
if (typeof usesHistory === 'object' && options === undefined) {
|
|
49
|
-
options = usesHistory;
|
|
50
|
-
usesHistory = false;
|
|
51
|
-
}
|
|
52
|
-
options = Object.assign({ password: false, passwordHash: '*', completer: new Function() }, options);
|
|
53
|
-
|
|
54
|
-
const stdin = process.stdin;
|
|
55
|
-
const stdout = process.stdout;
|
|
56
|
-
const useHistory = Boolean(usesHistory);
|
|
57
|
-
const key = useHistory ? History.getKey(question) : null;
|
|
58
|
-
let history = [];
|
|
59
|
-
let historyIndex = 0;
|
|
60
|
-
let buffer = '';
|
|
61
|
-
if (useHistory) {
|
|
62
|
-
if (!History.exists(key)) History.createHistory(key);
|
|
63
|
-
history = History.readHistory(key);
|
|
64
|
-
historyIndex = history.length;
|
|
65
|
-
}
|
|
66
|
-
let cursor = 0;
|
|
67
|
-
const password = Boolean(options.password);
|
|
68
|
-
const maskChar = String(options.passwordHash ?? '*');
|
|
69
|
-
const completer = typeof options.completer === 'function' ? options.completer : () => '';
|
|
70
|
-
|
|
71
|
-
const render = () => {
|
|
72
|
-
const display = password ? maskChar.repeat(buffer.length) : buffer;
|
|
73
|
-
stdout.write('\r\x1b[K' + question + display);
|
|
74
|
-
const moveLeft = display.length - cursor;
|
|
75
|
-
if (moveLeft > 0) stdout.write(`\x1b[${moveLeft}D`);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const setBuffer = (newBuffer, newCursor = newBuffer.length) => {
|
|
79
|
-
buffer = newBuffer;
|
|
80
|
-
cursor = Math.max(0, Math.min(newCursor, buffer.length));
|
|
81
|
-
render();
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const readKey = () => {
|
|
85
|
-
const buf = Buffer.alloc(4);
|
|
86
|
-
const bytes = fs.readSync(stdin.fd, buf, 0, 4, null);
|
|
87
|
-
return buf.toString('utf8', 0, bytes);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const enableRaw = () => {
|
|
91
|
-
if (stdin.isTTY && typeof stdin.setRawMode === 'function') {
|
|
92
|
-
stdin.setRawMode(true);
|
|
93
|
-
}
|
|
94
|
-
stdin.resume();
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const disableRaw = () => {
|
|
98
|
-
if (stdin.isTTY && typeof stdin.setRawMode === 'function') {
|
|
99
|
-
stdin.setRawMode(false);
|
|
100
|
-
}
|
|
101
|
-
stdin.pause();
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
enableRaw();
|
|
105
|
-
try {
|
|
106
|
-
render();
|
|
107
|
-
while (true) {
|
|
108
|
-
const keySeq = readKey();
|
|
109
|
-
if (!keySeq) continue;
|
|
110
|
-
|
|
111
|
-
if (keySeq === '\r' || keySeq === '\n') {
|
|
112
|
-
stdout.write('\n');
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (keySeq === '\u0003') {
|
|
117
|
-
disableRaw();
|
|
118
|
-
throw new Error('Interrupted');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (keySeq === '\u0008' || keySeq === '\x7f') {
|
|
122
|
-
if (cursor > 0) {
|
|
123
|
-
buffer = buffer.slice(0, cursor - 1) + buffer.slice(cursor);
|
|
124
|
-
cursor -= 1;
|
|
125
|
-
render();
|
|
126
|
-
}
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (keySeq === '\t') {
|
|
131
|
-
const completion = completer(buffer);
|
|
132
|
-
if (typeof completion === 'string' && completion.length > 0) {
|
|
133
|
-
setBuffer(completion, completion.length);
|
|
134
|
-
}
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (keySeq.startsWith('\u001b')) {
|
|
139
|
-
if (useHistory) {
|
|
140
|
-
if (keySeq === '\u001b[A') {
|
|
141
|
-
if (historyIndex > 0) {
|
|
142
|
-
historyIndex -= 1;
|
|
143
|
-
setBuffer(history[historyIndex] ?? '');
|
|
144
|
-
}
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (keySeq === '\u001b[B') {
|
|
148
|
-
if (historyIndex < history.length - 1) {
|
|
149
|
-
historyIndex += 1;
|
|
150
|
-
setBuffer(history[historyIndex] ?? '');
|
|
151
|
-
} else if (historyIndex === history.length - 1) {
|
|
152
|
-
historyIndex += 1;
|
|
153
|
-
setBuffer('');
|
|
154
|
-
}
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
// history disabled: ignore up/down keys
|
|
159
|
-
if (keySeq === '\u001b[A' || keySeq === '\u001b[B') {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (keySeq === '\u001b[C') {
|
|
164
|
-
if (cursor < buffer.length) {
|
|
165
|
-
cursor += 1;
|
|
166
|
-
render();
|
|
167
|
-
}
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
if (keySeq === '\u001b[D') {
|
|
171
|
-
if (cursor > 0) {
|
|
172
|
-
cursor -= 1;
|
|
173
|
-
render();
|
|
174
|
-
}
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (keySeq === '\u001b[3~') {
|
|
178
|
-
if (cursor < buffer.length) {
|
|
179
|
-
buffer = buffer.slice(0, cursor) + buffer.slice(cursor + 1);
|
|
180
|
-
render();
|
|
181
|
-
}
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (keySeq >= ' ' && keySeq <= '~') {
|
|
188
|
-
buffer = buffer.slice(0, cursor) + keySeq + buffer.slice(cursor);
|
|
189
|
-
cursor += keySeq.length;
|
|
190
|
-
render();
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
} finally {
|
|
195
|
-
disableRaw();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (useHistory) {
|
|
199
|
-
if (buffer !== '' && history[history.length - 1] !== buffer) {
|
|
200
|
-
History.appendHistory(key, buffer);
|
|
201
|
-
history = History.readHistory(key);
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
history = [];
|
|
205
|
-
}
|
|
53
|
+
};
|
|
206
54
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Shema (unchanged from original)
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
210
58
|
const ShemaTypes = {};
|
|
211
59
|
class ShemaType {
|
|
212
|
-
|
|
213
|
-
constructor(typename, validatorFunction) {
|
|
60
|
+
constructor(typename, validatorFunction, defaultObj) {
|
|
214
61
|
this.typename = typename;
|
|
215
62
|
this.validate = validatorFunction;
|
|
63
|
+
this.defaultObj
|
|
216
64
|
ShemaTypes[typename] = this;
|
|
217
65
|
}
|
|
218
66
|
}
|
|
219
67
|
class Shema {
|
|
220
|
-
constructor(obj) {
|
|
221
|
-
this.shema = obj;
|
|
222
|
-
}
|
|
68
|
+
constructor(obj) { this.shema = obj; }
|
|
223
69
|
validate(obj) {
|
|
224
70
|
for (const key in this.shema) {
|
|
225
71
|
const type = this.shema[key];
|
|
226
72
|
const value = obj[key];
|
|
227
73
|
if (type instanceof ShemaType) {
|
|
228
|
-
if (!type.validate(value))
|
|
74
|
+
if (!type.validate(value))
|
|
229
75
|
throw new Error(`Validation failed for key "${key}": expected ${type.typename}, got ${typeof value}`);
|
|
230
|
-
}
|
|
231
76
|
} else if (type instanceof Shema) {
|
|
232
77
|
type.validate(value);
|
|
233
78
|
} else {
|
|
@@ -241,14 +86,14 @@ class Shema {
|
|
|
241
86
|
const type = this.shema[key];
|
|
242
87
|
if (type instanceof ShemaType) {
|
|
243
88
|
obj[key] = type.typename === 'Int' || type.typename === 'Float' ? 0 :
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
89
|
+
type.typename === 'String' ? '' :
|
|
90
|
+
type.typename === 'Boolean' ? false :
|
|
91
|
+
type.typename === 'Array' ? [] :
|
|
92
|
+
type.typename === 'Object' ? {} :
|
|
93
|
+
type.typename === 'Function' ? () => { } :
|
|
94
|
+
type.typename === 'Null' ? null :
|
|
95
|
+
type.typename === 'Undefined' ? undefined :
|
|
96
|
+
type?.defaultObj ? type.defaultObj() : null;
|
|
252
97
|
} else if (type instanceof Shema) {
|
|
253
98
|
obj[key] = type.defaultObj();
|
|
254
99
|
} else {
|
|
@@ -258,74 +103,43 @@ class Shema {
|
|
|
258
103
|
return obj;
|
|
259
104
|
}
|
|
260
105
|
}
|
|
261
|
-
new ShemaType('Int',
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
new ShemaType('
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
new ShemaType('
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
new ShemaType('
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
new ShemaType('
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return typeof val === 'function';
|
|
281
|
-
});
|
|
282
|
-
new ShemaType('Null', function (val) {
|
|
283
|
-
return val === null;
|
|
284
|
-
});
|
|
285
|
-
new ShemaType('Undefined', function (val) {
|
|
286
|
-
return val === undefined;
|
|
287
|
-
});
|
|
288
|
-
new ShemaType('Symbol', function (val) {
|
|
289
|
-
return typeof val === 'symbol';
|
|
290
|
-
});
|
|
291
|
-
new ShemaType('BigInt', function (val) {
|
|
292
|
-
return typeof val === 'bigint';
|
|
293
|
-
});
|
|
294
|
-
new ShemaType('Date', function (val) {
|
|
295
|
-
return val instanceof Date && !isNaN(val);
|
|
296
|
-
});
|
|
297
|
-
new ShemaType('RegExp', function (val) {
|
|
298
|
-
return val instanceof RegExp;
|
|
299
|
-
});
|
|
300
|
-
new ShemaType('Shema', function (val) {
|
|
301
|
-
return val instanceof Shema;
|
|
302
|
-
});
|
|
303
|
-
new ShemaType('Any', function (val) {
|
|
304
|
-
return true;
|
|
305
|
-
});
|
|
106
|
+
new ShemaType('Int', v => Number.isInteger(v));
|
|
107
|
+
new ShemaType('Float', v => typeof v === 'number' && !Number.isNaN(v));
|
|
108
|
+
new ShemaType('String', v => typeof v === 'string');
|
|
109
|
+
new ShemaType('Boolean', v => typeof v === 'boolean');
|
|
110
|
+
new ShemaType('Array', v => Array.isArray(v));
|
|
111
|
+
new ShemaType('Object', v => v !== null && typeof v === 'object' && !Array.isArray(v));
|
|
112
|
+
new ShemaType('Function', v => typeof v === 'function');
|
|
113
|
+
new ShemaType('Null', v => v === null);
|
|
114
|
+
new ShemaType('Undefined', v => v === undefined);
|
|
115
|
+
new ShemaType('Symbol', v => typeof v === 'symbol');
|
|
116
|
+
new ShemaType('BigInt', v => typeof v === 'bigint');
|
|
117
|
+
new ShemaType('Date', v => v instanceof Date && !isNaN(v));
|
|
118
|
+
new ShemaType('RegExp', v => v instanceof RegExp);
|
|
119
|
+
new ShemaType('Shema', v => v instanceof Shema);
|
|
120
|
+
new ShemaType('Any', () => true);
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// ConfigFile (unchanged from original)
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
125
|
class ConfigFile {
|
|
307
|
-
constructor(name, type
|
|
308
|
-
// create Configs, like package.json... etc, write to them, read props, and even an interactive config repl like npm init with default values from the current config file if it exists or a selected default value.
|
|
126
|
+
constructor(name, type, hiddenFile = false, shema) {
|
|
309
127
|
this.path = hiddenFile ? '.134.' + name + '.' + type : name + '.135.' + type;
|
|
310
128
|
this.type = type;
|
|
311
129
|
this.shema = shema instanceof Shema ? shema : null;
|
|
312
|
-
if (!fs.existsSync(this.path))
|
|
130
|
+
if (!fs.existsSync(this.path))
|
|
313
131
|
fs.writeFileSync(this.path, this.type === 'json' ? JSON.parse(shema.defaultObj) : '');
|
|
314
|
-
}
|
|
315
132
|
}
|
|
316
133
|
writeFull(configObj) {
|
|
317
|
-
if (this.shema && this.shema instanceof Shema && this.shema.validate(configObj) === false)
|
|
134
|
+
if (this.shema && this.shema instanceof Shema && this.shema.validate(configObj) === false)
|
|
318
135
|
throw new Error('Config validation failed');
|
|
319
|
-
}
|
|
320
136
|
fs.writeFileSync(this.path, JSON.stringify(configObj));
|
|
321
137
|
}
|
|
322
|
-
write(key, value) {
|
|
138
|
+
write(key, value) {
|
|
323
139
|
const config = this.readFull();
|
|
324
140
|
let current = config;
|
|
325
141
|
for (let i = 0; i < key.length - 1; i++) {
|
|
326
|
-
if (typeof current[key[i]] !== 'object' || current[key[i]] === null) {
|
|
327
|
-
current[key[i]] = {};
|
|
328
|
-
}
|
|
142
|
+
if (typeof current[key[i]] !== 'object' || current[key[i]] === null) current[key[i]] = {};
|
|
329
143
|
current = current[key[i]];
|
|
330
144
|
}
|
|
331
145
|
current[key[key.length - 1]] = value;
|
|
@@ -333,52 +147,38 @@ class ConfigFile {
|
|
|
333
147
|
}
|
|
334
148
|
readFull() {
|
|
335
149
|
const content = fs.readFileSync(this.path, 'utf-8');
|
|
336
|
-
if (this.type === 'json')
|
|
337
|
-
|
|
338
|
-
} else {
|
|
339
|
-
throw new Error('Unsupported config type: ' + this.type);
|
|
340
|
-
}
|
|
150
|
+
if (this.type === 'json') return JSON.parse(content);
|
|
151
|
+
throw new Error('Unsupported config type: ' + this.type);
|
|
341
152
|
}
|
|
342
|
-
read(key) {
|
|
153
|
+
read(key) {
|
|
343
154
|
const config = this.readFull();
|
|
344
155
|
let current = config;
|
|
345
156
|
for (let i = 0; i < key.length; i++) {
|
|
346
|
-
if (typeof current[key[i]] !== 'object' || current[key[i]] === null)
|
|
347
|
-
return undefined;
|
|
348
|
-
}
|
|
157
|
+
if (typeof current[key[i]] !== 'object' || current[key[i]] === null) return undefined;
|
|
349
158
|
current = current[key[i]];
|
|
350
159
|
}
|
|
351
160
|
return current;
|
|
352
161
|
}
|
|
353
|
-
interactiveSetup(defaultConfig = {}) {
|
|
162
|
+
interactiveSetup(defaultConfig = {}) {
|
|
354
163
|
const config = this.readFull();
|
|
355
164
|
const mergedConfig = { ...defaultConfig, ...config };
|
|
356
165
|
const questions = [];
|
|
357
166
|
const buildQuestions = (obj, path = []) => {
|
|
358
167
|
for (const key in obj) {
|
|
359
168
|
const value = obj[key];
|
|
360
|
-
if (typeof value === 'object' && value !== null)
|
|
361
|
-
|
|
362
|
-
} else {
|
|
363
|
-
questions.push({ path: path.concat(key), default: value });
|
|
364
|
-
}
|
|
169
|
+
if (typeof value === 'object' && value !== null) buildQuestions(value, path.concat(key));
|
|
170
|
+
else questions.push({ path: path.concat(key), default: value });
|
|
365
171
|
}
|
|
366
172
|
};
|
|
367
173
|
buildQuestions(mergedConfig);
|
|
368
174
|
const askQuestion = (index) => {
|
|
369
|
-
if (index >= questions.length) {
|
|
370
|
-
this.writeFull(mergedConfig);
|
|
371
|
-
console.log('Config saved successfully!');
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
175
|
+
if (index >= questions.length) { this.writeFull(mergedConfig); console.log('Config saved successfully!'); return; }
|
|
374
176
|
const { path, default: defaultValue } = questions[index];
|
|
375
|
-
|
|
177
|
+
InputMgr.prompt(`Enter value for ${path.join('.')} (default: ${defaultValue}): `).then(answer => {
|
|
376
178
|
if (answer.trim() !== '') {
|
|
377
179
|
let current = mergedConfig;
|
|
378
180
|
for (let i = 0; i < path.length - 1; i++) {
|
|
379
|
-
if (typeof current[path[i]] !== 'object' || current[path[i]] === null) {
|
|
380
|
-
current[path[i]] = {};
|
|
381
|
-
}
|
|
181
|
+
if (typeof current[path[i]] !== 'object' || current[path[i]] === null) current[path[i]] = {};
|
|
382
182
|
current = current[path[i]];
|
|
383
183
|
}
|
|
384
184
|
current[path[path.length - 1]] = answer;
|
|
@@ -389,17 +189,17 @@ class ConfigFile {
|
|
|
389
189
|
askQuestion(0);
|
|
390
190
|
}
|
|
391
191
|
}
|
|
192
|
+
|
|
193
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
194
|
+
// JsDB / Storage / Zip / Crypto / PathUtils / Base64 / OsUtils (unchanged)
|
|
195
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
392
196
|
let JsDB = {
|
|
393
|
-
class(JsClass) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return eval(code);
|
|
398
|
-
},
|
|
399
|
-
require,
|
|
197
|
+
class(JsClass) { return (...args) => new JsClass(...args); },
|
|
198
|
+
run(code) { return eval(code); },
|
|
199
|
+
require: (p) => require(path.join(process.cwd(), p)),
|
|
200
|
+
reqRaw: require,
|
|
400
201
|
gt: globalThis,
|
|
401
202
|
fetch(url, options) {
|
|
402
|
-
//sync fetch using child_process, cross-platform compatible
|
|
403
203
|
const { execSync } = require('child_process');
|
|
404
204
|
let command;
|
|
405
205
|
if (process.platform === 'win32') {
|
|
@@ -407,18 +207,15 @@ let JsDB = {
|
|
|
407
207
|
} else {
|
|
408
208
|
command = `curl -X ${options?.method || 'GET'} '${url}' ${Object.entries(options?.headers || {}).map(([k, v]) => `-H "${k}: ${v}"`).join(' ')} -d '${options?.body || ''}'`;
|
|
409
209
|
}
|
|
410
|
-
try {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
platform: process.platform,
|
|
420
|
-
env: process.env
|
|
421
|
-
}
|
|
210
|
+
try { return execSync(command, { encoding: 'utf-8' }); }
|
|
211
|
+
catch (error) { throw new Error(`Fetch failed: ${error.message}`); }
|
|
212
|
+
},
|
|
213
|
+
process,
|
|
214
|
+
version: process.version,
|
|
215
|
+
platform: process.platform,
|
|
216
|
+
env: process.env
|
|
217
|
+
};
|
|
218
|
+
|
|
422
219
|
const Storage = {
|
|
423
220
|
setItem(key, value) {
|
|
424
221
|
const filePath = path.join(__dirname, '.storage', key);
|
|
@@ -427,25 +224,19 @@ const Storage = {
|
|
|
427
224
|
},
|
|
428
225
|
getItem(key) {
|
|
429
226
|
const filePath = path.join(__dirname, '.storage', key);
|
|
430
|
-
if (fs.existsSync(filePath))
|
|
431
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
432
|
-
return JSON.parse(content);
|
|
433
|
-
}
|
|
227
|
+
if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
434
228
|
return null;
|
|
435
229
|
},
|
|
436
230
|
removeItem(key) {
|
|
437
231
|
const filePath = path.join(__dirname, '.storage', key);
|
|
438
|
-
if (fs.existsSync(filePath))
|
|
439
|
-
fs.unlinkSync(filePath);
|
|
440
|
-
}
|
|
232
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
441
233
|
},
|
|
442
234
|
clear() {
|
|
443
235
|
const storageDir = path.join(__dirname, '.storage');
|
|
444
|
-
if (fs.existsSync(storageDir)) {
|
|
445
|
-
fs.rmSync(storageDir, { recursive: true, force: true });
|
|
446
|
-
}
|
|
236
|
+
if (fs.existsSync(storageDir)) fs.rmSync(storageDir, { recursive: true, force: true });
|
|
447
237
|
}
|
|
448
238
|
};
|
|
239
|
+
|
|
449
240
|
const Zip = {
|
|
450
241
|
zip(files, output) {
|
|
451
242
|
const archiver = require('archiver');
|
|
@@ -455,12 +246,7 @@ const Zip = {
|
|
|
455
246
|
outputStream.on('close', () => resolve());
|
|
456
247
|
archive.on('error', err => reject(err));
|
|
457
248
|
archive.pipe(outputStream);
|
|
458
|
-
files.forEach(file => {
|
|
459
|
-
if (fs.existsSync(file)) {
|
|
460
|
-
archive.file(file, { name: path.basename(file) });
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
);
|
|
249
|
+
files.forEach(file => { if (fs.existsSync(file)) archive.file(file, { name: path.basename(file) }); });
|
|
464
250
|
archive.finalize();
|
|
465
251
|
});
|
|
466
252
|
},
|
|
@@ -474,36 +260,24 @@ const Zip = {
|
|
|
474
260
|
});
|
|
475
261
|
}
|
|
476
262
|
};
|
|
263
|
+
|
|
477
264
|
const Crypto = {
|
|
478
|
-
hash(data, algorithm = 'sha256') {
|
|
479
|
-
return hash(algorithm).update(data).digest('hex');
|
|
480
|
-
}
|
|
265
|
+
hash(data, algorithm = 'sha256') { return hash(algorithm).update(data).digest('hex'); }
|
|
481
266
|
};
|
|
267
|
+
|
|
482
268
|
const PathUtils = {
|
|
483
|
-
join(...segments) {
|
|
484
|
-
|
|
485
|
-
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
},
|
|
489
|
-
basename(p, ext) {
|
|
490
|
-
return path.basename(p, ext);
|
|
491
|
-
},
|
|
492
|
-
dirname(p) {
|
|
493
|
-
return path.dirname(p);
|
|
494
|
-
},
|
|
495
|
-
extname(p) {
|
|
496
|
-
return path.extname(p);
|
|
497
|
-
}
|
|
269
|
+
join(...segments) { return path.join(...segments); },
|
|
270
|
+
resolve(...segments) { return path.resolve(...segments); },
|
|
271
|
+
basename(p, ext) { return path.basename(p, ext); },
|
|
272
|
+
dirname(p) { return path.dirname(p); },
|
|
273
|
+
extname(p) { return path.extname(p); }
|
|
498
274
|
};
|
|
275
|
+
|
|
499
276
|
const Base64 = {
|
|
500
|
-
encode(str) {
|
|
501
|
-
|
|
502
|
-
},
|
|
503
|
-
decode(encoded) {
|
|
504
|
-
return Buffer.from(encoded, 'base64').toString('utf-8');
|
|
505
|
-
}
|
|
277
|
+
encode(str) { return Buffer.from(str, 'utf-8').toString('base64'); },
|
|
278
|
+
decode(encoded) { return Buffer.from(encoded, 'base64').toString('utf-8'); }
|
|
506
279
|
};
|
|
280
|
+
|
|
507
281
|
const OsUtils = {
|
|
508
282
|
platform: process.platform,
|
|
509
283
|
homedir: require('os').homedir,
|
|
@@ -513,47 +287,749 @@ const OsUtils = {
|
|
|
513
287
|
freemem: require('os').freemem,
|
|
514
288
|
uptime: require('os').uptime
|
|
515
289
|
};
|
|
516
|
-
switch (process.platform) {
|
|
517
|
-
case 'win32':
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
290
|
+
switch (process.platform) {
|
|
291
|
+
case 'win32':
|
|
292
|
+
OsUtils.requestAdminPrivileges = function () {
|
|
293
|
+
try { execSync('powershell -Command "Start-Process cmd -Verb runAs"'); }
|
|
294
|
+
catch (error) { throw new Error('Failed to request admin privileges: ' + error.message); }
|
|
295
|
+
};
|
|
296
|
+
OsUtils.openFile = function (filePath) {
|
|
297
|
+
try { execSync(`start "" "${filePath}"`); }
|
|
298
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
299
|
+
};
|
|
300
|
+
break;
|
|
301
|
+
case 'darwin':
|
|
302
|
+
OsUtils.openFile = function (filePath) {
|
|
303
|
+
try { execSync(`open "${filePath}"`); }
|
|
304
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
305
|
+
};
|
|
306
|
+
break;
|
|
307
|
+
case 'linux':
|
|
308
|
+
OsUtils.openFile = function (filePath) {
|
|
309
|
+
try { execSync(`xdg-open "${filePath}"`); }
|
|
310
|
+
catch (error) { throw new Error('Failed to open file: ' + error.message); }
|
|
311
|
+
};
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// MathExprDebugger (unchanged from original)
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
class MathExprDebugger {
|
|
319
|
+
constructor(value) { this.value = value.toString(); }
|
|
320
|
+
__operators = {
|
|
321
|
+
unary: {
|
|
322
|
+
'+': () => new MathExprDebugger(this.value),
|
|
323
|
+
'-': () => new MathExprDebugger('( -' + this.value + ' )')
|
|
324
|
+
},
|
|
325
|
+
binary: {
|
|
326
|
+
'+': (a) => new MathExprDebugger(`(${this.value} + ${a.value})`),
|
|
327
|
+
'-': (a) => new MathExprDebugger(`(${this.value} - ${a.value})`),
|
|
328
|
+
'*': (a) => new MathExprDebugger(`(${this.value} * ${a.value})`),
|
|
329
|
+
'/': (a) => new MathExprDebugger(`(${this.value} / ${a.value})`)
|
|
535
330
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
331
|
+
};
|
|
332
|
+
toString() { return this.value; }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
336
|
+
//
|
|
337
|
+
// I N P U T M G R — Full terminal input manager for novac / bstd
|
|
338
|
+
//
|
|
339
|
+
// Features:
|
|
340
|
+
// · prompt(msg, opts) — sync line read (main API)
|
|
341
|
+
// · promptAsync(msg, opts) — async line read
|
|
342
|
+
// · promptPassword(msg) — sync masked/hashed password read
|
|
343
|
+
// · promptPasswordAsync(msg) — async masked/hashed password read
|
|
344
|
+
// · repl(opts) — sync REPL loop
|
|
345
|
+
// · replAsync(opts) — async REPL loop
|
|
346
|
+
// · mouse(cb, opts) — terminal mouse/click event listener (async)
|
|
347
|
+
// · SyntaxHighlight — pluggable syntax highlight API
|
|
348
|
+
// · history management — per-name persistent JSON history files
|
|
349
|
+
//
|
|
350
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
351
|
+
|
|
352
|
+
// ── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
353
|
+
const ESC = '\x1b';
|
|
354
|
+
const _ansi = {
|
|
355
|
+
reset: `${ESC}[0m`,
|
|
356
|
+
bold: `${ESC}[1m`,
|
|
357
|
+
dim: `${ESC}[2m`,
|
|
358
|
+
hide: `${ESC}[?25l`,
|
|
359
|
+
show: `${ESC}[?25h`,
|
|
360
|
+
clearLine: `${ESC}[2K`,
|
|
361
|
+
bol: `${ESC}[0G`,
|
|
362
|
+
up: (n = 1) => `${ESC}[${n}A`,
|
|
363
|
+
down: (n = 1) => `${ESC}[${n}B`,
|
|
364
|
+
right: (n = 1) => `${ESC}[${n}C`,
|
|
365
|
+
left: (n = 1) => `${ESC}[${n}D`,
|
|
366
|
+
col: (n) => `${ESC}[${n}G`,
|
|
367
|
+
mouseOn: `${ESC}[?1000h${ESC}[?1002h${ESC}[?1006h`,
|
|
368
|
+
mouseOff: `${ESC}[?1006l${ESC}[?1002l${ESC}[?1000l`,
|
|
369
|
+
fg: (c) => `${ESC}[${c}m`,
|
|
370
|
+
saveCursor: `${ESC}[s`,
|
|
371
|
+
restCursor: `${ESC}[u`,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// ── syncReadLine — the core synchronous stdin line reader ─────────────────────
|
|
375
|
+
// Reads one line from stdin in raw mode, honouring:
|
|
376
|
+
// mask — character to echo instead of real chars (password mode)
|
|
377
|
+
// history — array of previous entries for ↑/↓ navigation
|
|
378
|
+
// highlight — function(line) → coloured string for display
|
|
379
|
+
// placeholder — greyed-out text shown when buffer is empty
|
|
380
|
+
function _syncReadLine(promptStr, { mask = null, history = [], highlight = null, placeholder = '' } = {}) {
|
|
381
|
+
const stdin = process.stdin;
|
|
382
|
+
const stdout = process.stdout;
|
|
383
|
+
|
|
384
|
+
// Save / restore terminal settings
|
|
385
|
+
const wasTTY = stdin.isTTY;
|
|
386
|
+
const wasRaw = wasTTY && stdin.isRaw;
|
|
387
|
+
|
|
388
|
+
if (wasTTY) stdin.setRawMode(true);
|
|
389
|
+
stdin.resume();
|
|
390
|
+
|
|
391
|
+
let buf = '';
|
|
392
|
+
let cursor = 0; // insertion point in buf
|
|
393
|
+
let histIdx = history.length; // pointer: history.length = "current" (fresh) line
|
|
394
|
+
let saved = ''; // preserves current input when navigating history
|
|
395
|
+
|
|
396
|
+
// Write prompt + current buffer, clear to end of line
|
|
397
|
+
const redraw = () => {
|
|
398
|
+
const display = mask
|
|
399
|
+
? mask.repeat(buf.length)
|
|
400
|
+
: (highlight ? highlight(buf) : buf);
|
|
401
|
+
const shown = buf.length === 0 && placeholder
|
|
402
|
+
? `${_ansi.dim}${placeholder}${_ansi.reset}`
|
|
403
|
+
: display;
|
|
404
|
+
// Go to BOL, clear line, rewrite
|
|
405
|
+
stdout.write(`\r${_ansi.clearLine}${promptStr}${shown}`);
|
|
406
|
+
// Reposition cursor
|
|
407
|
+
const visLen = (mask ? mask.length * cursor : promptStr.length + cursor);
|
|
408
|
+
// We know promptStr printable length is promptStr.replace(/\x1b\[[^m]*m/g,'').length
|
|
409
|
+
const promptVis = promptStr.replace(/\x1b\[[^m]*m/g, '').length;
|
|
410
|
+
stdout.write(_ansi.col(promptVis + cursor + 1));
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
redraw();
|
|
414
|
+
|
|
415
|
+
let line = null;
|
|
416
|
+
// Read bytes synchronously in a tight loop
|
|
417
|
+
const buf8 = Buffer.alloc(8);
|
|
418
|
+
|
|
419
|
+
while (line === null) {
|
|
420
|
+
const n = fs.readSync(stdin.fd, buf8, 0, buf8.length, null);
|
|
421
|
+
if (n <= 0) { line = buf; break; }
|
|
422
|
+
let seq = buf8.slice(0, n).toString('utf8');
|
|
423
|
+
|
|
424
|
+
// ── Escape sequences ───────────────────────────────────────────────────
|
|
425
|
+
if (seq === '\x1b[A' || seq === '\x1bOA') {
|
|
426
|
+
// Arrow UP — history prev
|
|
427
|
+
if (histIdx > 0) {
|
|
428
|
+
if (histIdx === history.length) saved = buf;
|
|
429
|
+
histIdx--;
|
|
430
|
+
buf = history[histIdx];
|
|
431
|
+
cursor = buf.length;
|
|
432
|
+
redraw();
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (seq === '\x1b[B' || seq === '\x1bOB') {
|
|
437
|
+
// Arrow DOWN — history next
|
|
438
|
+
if (histIdx < history.length) {
|
|
439
|
+
histIdx++;
|
|
440
|
+
buf = histIdx === history.length ? saved : history[histIdx];
|
|
441
|
+
cursor = buf.length;
|
|
442
|
+
redraw();
|
|
443
|
+
}
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (seq === '\x1b[C' || seq === '\x1bOC') {
|
|
447
|
+
// Arrow RIGHT
|
|
448
|
+
if (cursor < buf.length) { cursor++; redraw(); }
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (seq === '\x1b[D' || seq === '\x1bOD') {
|
|
452
|
+
// Arrow LEFT
|
|
453
|
+
if (cursor > 0) { cursor--; redraw(); }
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (seq === '\x1b[H' || seq === '\x1bOH' || seq === '\x01') {
|
|
457
|
+
// Home / Ctrl-A
|
|
458
|
+
cursor = 0; redraw(); continue;
|
|
459
|
+
}
|
|
460
|
+
if (seq === '\x1b[F' || seq === '\x1bOF' || seq === '\x05') {
|
|
461
|
+
// End / Ctrl-E
|
|
462
|
+
cursor = buf.length; redraw(); continue;
|
|
463
|
+
}
|
|
464
|
+
if (seq === '\x1b[3~') {
|
|
465
|
+
// Delete key
|
|
466
|
+
if (cursor < buf.length) { buf = buf.slice(0, cursor) + buf.slice(cursor + 1); redraw(); }
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
// Ctrl-U — clear line
|
|
470
|
+
if (seq === '\x15') { buf = ''; cursor = 0; redraw(); continue; }
|
|
471
|
+
// Ctrl-K — clear from cursor to end
|
|
472
|
+
if (seq === '\x0b') { buf = buf.slice(0, cursor); redraw(); continue; }
|
|
473
|
+
// Ctrl-W — delete word before cursor
|
|
474
|
+
if (seq === '\x17') {
|
|
475
|
+
const left = buf.slice(0, cursor).replace(/\S+\s*$/, '');
|
|
476
|
+
buf = left + buf.slice(cursor);
|
|
477
|
+
cursor = left.length;
|
|
478
|
+
redraw(); continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ── Single characters ──────────────────────────────────────────────────
|
|
482
|
+
for (let i = 0; i < seq.length; i++) {
|
|
483
|
+
const ch = seq[i];
|
|
484
|
+
const code = ch.charCodeAt(0);
|
|
485
|
+
|
|
486
|
+
if (ch === '\r' || ch === '\n') {
|
|
487
|
+
// Enter
|
|
488
|
+
stdout.write('\n');
|
|
489
|
+
line = buf;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
if (code === 127 || code === 8) {
|
|
493
|
+
// Backspace
|
|
494
|
+
if (cursor > 0) { buf = buf.slice(0, cursor - 1) + buf.slice(cursor); cursor--; redraw(); }
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (ch === '\x03') {
|
|
498
|
+
// Ctrl-C
|
|
499
|
+
stdout.write('^C\n');
|
|
500
|
+
if (wasTTY) stdin.setRawMode(wasRaw);
|
|
501
|
+
stdin.pause();
|
|
502
|
+
process.exit(130);
|
|
503
|
+
}
|
|
504
|
+
if (ch === '\x04') {
|
|
505
|
+
// Ctrl-D (EOF)
|
|
506
|
+
stdout.write('\n');
|
|
507
|
+
line = buf;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
if (code >= 32) {
|
|
511
|
+
// Printable
|
|
512
|
+
buf = buf.slice(0, cursor) + ch + buf.slice(cursor);
|
|
513
|
+
cursor++;
|
|
514
|
+
redraw();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (wasTTY) stdin.setRawMode(wasRaw);
|
|
520
|
+
stdin.pause();
|
|
521
|
+
|
|
522
|
+
return line;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── SyntaxHighlight — pluggable highlighter registry ─────────────────────────
|
|
526
|
+
//
|
|
527
|
+
// Usage:
|
|
528
|
+
// SyntaxHighlight.register('myLang', (line) => coloredLine);
|
|
529
|
+
// const hl = SyntaxHighlight.get('myLang');
|
|
530
|
+
// SyntaxHighlight.highlight('myLang', 'some code');
|
|
531
|
+
//
|
|
532
|
+
// Built-in themes: 'novac', 'json', 'shell', 'basic', 'bf'
|
|
533
|
+
const SyntaxHighlight = (() => {
|
|
534
|
+
const _reg = new Map();
|
|
535
|
+
|
|
536
|
+
// ── colour helpers (no deps) ────────────────────────────────────────────
|
|
537
|
+
const C = {
|
|
538
|
+
reset: '\x1b[0m',
|
|
539
|
+
keyword: '\x1b[35m', // magenta
|
|
540
|
+
string: '\x1b[32m', // green
|
|
541
|
+
number: '\x1b[33m', // yellow
|
|
542
|
+
comment: '\x1b[90m', // dark grey
|
|
543
|
+
ident: '\x1b[36m', // cyan
|
|
544
|
+
op: '\x1b[37m', // white
|
|
545
|
+
error: '\x1b[31m', // red
|
|
546
|
+
prompt: '\x1b[94m', // bright blue
|
|
547
|
+
dim: '\x1b[2m',
|
|
548
|
+
bold: '\x1b[1m',
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// paint helper: wrap text in a colour and reset
|
|
552
|
+
const paint = (colour, text) => colour + text + C.reset;
|
|
553
|
+
|
|
554
|
+
// ── Nova/novac highlighter ──────────────────────────────────────────────
|
|
555
|
+
const _novacKeywords = new Set([
|
|
556
|
+
'var', 'let', 'const', 'class', 'if', 'else', 'for', 'while', 'do', 'return', 'give',
|
|
557
|
+
'func', 'function', 'async', 'await', 'new', 'import', 'export', 'from', 'switch',
|
|
558
|
+
'case', 'default', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'emit',
|
|
559
|
+
'on', 'of', 'each', 'match', 'when', 'where', 'as', 'type', 'struct', 'interface',
|
|
560
|
+
'enum', 'trait', 'impl', 'yield', 'loop', 'repeat', 'unless', 'until', 'block',
|
|
561
|
+
'assert', 'guard', 'with', 'run', 'true', 'false', 'null',
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
const _hlNovac = (line) => {
|
|
565
|
+
// Tokenise very simply: strings, numbers, keywords, idents, operators
|
|
566
|
+
let out = '';
|
|
567
|
+
let i = 0;
|
|
568
|
+
while (i < line.length) {
|
|
569
|
+
// Comment
|
|
570
|
+
if (line[i] === '/' && (line[i + 1] === '/' || line[i + 1] === '!')) {
|
|
571
|
+
out += paint(C.comment, line.slice(i)); break;
|
|
572
|
+
}
|
|
573
|
+
// String (single or double)
|
|
574
|
+
if (line[i] === '"' || line[i] === "'") {
|
|
575
|
+
const q = line[i]; let j = i + 1;
|
|
576
|
+
while (j < line.length && line[j] !== q) { if (line[j] === '\\') j++; j++; }
|
|
577
|
+
out += paint(C.string, line.slice(i, j + 1)); i = j + 1; continue;
|
|
578
|
+
}
|
|
579
|
+
// f-string prefix
|
|
580
|
+
if (line[i] === 'f' && (line[i + 1] === '"' || line[i + 1] === "'")) {
|
|
581
|
+
const q = line[i + 1]; let j = i + 2;
|
|
582
|
+
while (j < line.length && line[j] !== q) { if (line[j] === '\\') j++; j++; }
|
|
583
|
+
out += paint(C.string, line.slice(i, j + 1)); i = j + 1; continue;
|
|
584
|
+
}
|
|
585
|
+
// Number
|
|
586
|
+
if (/[0-9]/.test(line[i])) {
|
|
587
|
+
let j = i;
|
|
588
|
+
while (j < line.length && /[0-9._xXbBoOeE+\-]/.test(line[j])) j++;
|
|
589
|
+
out += paint(C.number, line.slice(i, j)); i = j; continue;
|
|
590
|
+
}
|
|
591
|
+
// Identifier / keyword
|
|
592
|
+
if (/[A-Za-z_$]/.test(line[i])) {
|
|
593
|
+
let j = i;
|
|
594
|
+
while (j < line.length && /[A-Za-z0-9_$]/.test(line[j])) j++;
|
|
595
|
+
const word = line.slice(i, j);
|
|
596
|
+
out += _novacKeywords.has(word) ? paint(C.keyword, word) : paint(C.ident, word);
|
|
597
|
+
i = j; continue;
|
|
598
|
+
}
|
|
599
|
+
// Operators & punctuation — keep plain
|
|
600
|
+
out += paint(C.op, line[i]); i++;
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// ── JSON highlighter ────────────────────────────────────────────────────
|
|
606
|
+
const _hlJSON = (line) => {
|
|
607
|
+
return line
|
|
608
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, s => paint(C.string, s))
|
|
609
|
+
.replace(/\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g, s => paint(C.number, s))
|
|
610
|
+
.replace(/\b(true|false|null)\b/g, s => paint(C.keyword, s));
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// ── Shell highlighter ───────────────────────────────────────────────────
|
|
614
|
+
const _shellKw = new Set(['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'case', 'esac', 'function', 'return', 'exit', 'echo', 'cd', 'ls', 'mkdir', 'rm', 'cp', 'mv', 'grep', 'sed', 'awk', 'cat', 'source', 'export', 'alias']);
|
|
615
|
+
const _hlShell = (line) => {
|
|
616
|
+
if (line.trimStart().startsWith('#')) return paint(C.comment, line);
|
|
617
|
+
let out = '';
|
|
618
|
+
const tokens = line.split(/(\s+|[|>&;(){}[\]]|"(?:[^"\\]|\\.)*"|'[^']*')/);
|
|
619
|
+
let first = true;
|
|
620
|
+
for (const tok of tokens) {
|
|
621
|
+
if (!tok) continue;
|
|
622
|
+
if (first && /[A-Za-z_]/.test(tok[0])) {
|
|
623
|
+
out += _shellKw.has(tok) ? paint(C.keyword, tok) : paint(C.prompt, tok);
|
|
624
|
+
first = false;
|
|
625
|
+
} else if (tok.startsWith('"') || tok.startsWith("'")) {
|
|
626
|
+
out += paint(C.string, tok);
|
|
627
|
+
} else if (/^-?[0-9]+$/.test(tok)) {
|
|
628
|
+
out += paint(C.number, tok);
|
|
629
|
+
} else if (/^[|>&;(){}[\]]$/.test(tok.trim())) {
|
|
630
|
+
out += paint(C.op, tok);
|
|
631
|
+
} else {
|
|
632
|
+
out += tok;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return out;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// ── BASIC highlighter ───────────────────────────────────────────────────
|
|
639
|
+
const _basicKw = new Set([
|
|
640
|
+
'PRINT', 'INPUT', 'LET', 'IF', 'THEN', 'ELSE', 'GOTO', 'GOSUB', 'RETURN', 'END',
|
|
641
|
+
'FOR', 'TO', 'STEP', 'NEXT', 'DIM', 'DATA', 'READ', 'RESTORE', 'REM', 'STOP',
|
|
642
|
+
'LIST', 'RUN', 'NEW', 'LOAD', 'SAVE', 'CLS', 'CLR', 'AND', 'OR', 'NOT',
|
|
643
|
+
'INT', 'SQR', 'ABS', 'RND', 'SIN', 'COS', 'TAN', 'LOG', 'EXP', 'SGN',
|
|
644
|
+
'MID$', 'LEFT$', 'RIGHT$', 'LEN', 'STR$', 'VAL', 'CHR$', 'ASC', 'INSTR',
|
|
645
|
+
'TAB', 'SPC', 'AT', 'POKE', 'PEEK', 'CALL', 'ON',
|
|
646
|
+
]);
|
|
647
|
+
const _hlBasic = (line) => {
|
|
648
|
+
// line-number prefix
|
|
649
|
+
const lineNo = line.match(/^(\s*\d+\s)/);
|
|
650
|
+
let rest = lineNo ? line.slice(lineNo[0].length) : line;
|
|
651
|
+
let prefix = lineNo ? paint(C.number, lineNo[0]) : '';
|
|
652
|
+
// REM comment
|
|
653
|
+
if (/^\s*REM\b/i.test(rest)) return prefix + paint(C.comment, rest);
|
|
654
|
+
let out = '';
|
|
655
|
+
const tokens = rest.split(/(\b[A-Z$]+\b|\d+(?:\.\d+)?|"[^"]*"|[^A-Za-z0-9"$\s]+|\s+)/gi);
|
|
656
|
+
for (const tok of tokens) {
|
|
657
|
+
if (!tok) continue;
|
|
658
|
+
if (_basicKw.has(tok.toUpperCase())) out += paint(C.keyword, tok);
|
|
659
|
+
else if (tok.startsWith('"')) out += paint(C.string, tok);
|
|
660
|
+
else if (/^-?\d/.test(tok)) out += paint(C.number, tok);
|
|
661
|
+
else out += tok;
|
|
662
|
+
}
|
|
663
|
+
return prefix + out;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// ── Brainfuck highlighter ───────────────────────────────────────────────
|
|
667
|
+
const _hlBF = (line) => {
|
|
668
|
+
return [...line].map(c => {
|
|
669
|
+
if ('><'.includes(c)) return paint(C.ident, c);
|
|
670
|
+
if ('+-'.includes(c)) return paint(C.keyword, c);
|
|
671
|
+
if ('.,'.includes(c)) return paint(C.string, c);
|
|
672
|
+
if ('[]'.includes(c)) return paint(C.op, c);
|
|
673
|
+
return paint(C.comment, c);
|
|
674
|
+
}).join('');
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// Register built-ins
|
|
678
|
+
_reg.set('novac', _hlNovac);
|
|
679
|
+
_reg.set('nova', _hlNovac);
|
|
680
|
+
_reg.set('json', _hlJSON);
|
|
681
|
+
_reg.set('shell', _hlShell);
|
|
682
|
+
_reg.set('bash', _hlShell);
|
|
683
|
+
_reg.set('basic', _hlBasic);
|
|
684
|
+
_reg.set('bf', _hlBF);
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
/** Register a custom highlighter: fn(line: string) → string */
|
|
688
|
+
register(name, fn) { _reg.set(name, fn); },
|
|
689
|
+
/** Get a highlighter by name (or null) */
|
|
690
|
+
get(name) { return _reg.get(name) || null; },
|
|
691
|
+
/** Highlight a line with a named theme */
|
|
692
|
+
highlight(name, line) {
|
|
693
|
+
const fn = _reg.get(name);
|
|
694
|
+
return fn ? fn(line) : line;
|
|
695
|
+
},
|
|
696
|
+
/** List registered theme names */
|
|
697
|
+
list() { return [..._reg.keys()]; },
|
|
698
|
+
/** Raw colour constants for custom highlighters */
|
|
699
|
+
colors: C,
|
|
700
|
+
paint,
|
|
701
|
+
};
|
|
702
|
+
})();
|
|
703
|
+
|
|
704
|
+
// ── InputMgr — main export ────────────────────────────────────────────────────
|
|
705
|
+
const InputMgr = (() => {
|
|
706
|
+
|
|
707
|
+
// ── internal history manager ──────────────────────────────────────────────
|
|
708
|
+
//
|
|
709
|
+
// Histories are stored in hidden JSON files: `.184.<name>`
|
|
710
|
+
// The same format used by the existing `History` object in bstd, but
|
|
711
|
+
// InputMgr manages them automatically per prompt name.
|
|
712
|
+
//
|
|
713
|
+
const _histCache = new Map(); // name → array of strings (in-memory)
|
|
714
|
+
|
|
715
|
+
function _histKey(name) { return '.184.inputmgr.' + name; }
|
|
716
|
+
|
|
717
|
+
function _histLoad(name) {
|
|
718
|
+
if (_histCache.has(name)) return _histCache.get(name);
|
|
719
|
+
const key = _histKey(name);
|
|
720
|
+
let arr = [];
|
|
721
|
+
if (fs.existsSync(key)) {
|
|
722
|
+
try { arr = JSON.parse(fs.readFileSync(key, 'utf8')).history || []; }
|
|
723
|
+
catch (_) { arr = []; }
|
|
724
|
+
}
|
|
725
|
+
_histCache.set(name, arr);
|
|
726
|
+
return arr;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function _histSave(name, arr) {
|
|
730
|
+
_histCache.set(name, arr);
|
|
731
|
+
try { fs.writeFileSync(_histKey(name), JSON.stringify({ history: arr })); }
|
|
732
|
+
catch (_) { /* best-effort */ }
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function _histPush(name, line) {
|
|
736
|
+
if (!line || !line.trim()) return;
|
|
737
|
+
const arr = _histLoad(name);
|
|
738
|
+
// Deduplicate: remove previous identical entry, push to end
|
|
739
|
+
const idx = arr.lastIndexOf(line);
|
|
740
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
741
|
+
arr.push(line);
|
|
742
|
+
// Cap at 500 entries
|
|
743
|
+
if (arr.length > 500) arr.splice(0, arr.length - 500);
|
|
744
|
+
_histSave(name, arr);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ── password hashing ────────────────────────────────────────────────────
|
|
748
|
+
//
|
|
749
|
+
// Returns: { raw, hash, algo, verify(input) }
|
|
750
|
+
//
|
|
751
|
+
function _hashPassword(raw, algo = 'sha256') {
|
|
752
|
+
const hashed = crypto.createHash(algo).update(raw).digest('hex');
|
|
753
|
+
return {
|
|
754
|
+
raw,
|
|
755
|
+
hash: hashed,
|
|
756
|
+
algo,
|
|
757
|
+
verify: (input) => crypto.createHash(algo).update(input).digest('hex') === hashed,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ── core prompt (sync) ──────────────────────────────────────────────────
|
|
762
|
+
//
|
|
763
|
+
// opts:
|
|
764
|
+
// password {boolean} — mask input with '•'
|
|
765
|
+
// mask {string} — custom mask char (default '•')
|
|
766
|
+
// historyKey {string|false} — key for history persistence (default 'default')
|
|
767
|
+
// highlight {string|fn|false} — theme name or custom fn
|
|
768
|
+
// placeholder{string} — grey hint when empty
|
|
769
|
+
// hash {string|false} — hash algorithm for password result (sha256)
|
|
770
|
+
//
|
|
771
|
+
function prompt(message = '> ', opts = {}) {
|
|
772
|
+
const {
|
|
773
|
+
password = false,
|
|
774
|
+
mask = '•',
|
|
775
|
+
historyKey = 'default',
|
|
776
|
+
highlight = false,
|
|
777
|
+
placeholder = '',
|
|
778
|
+
hash = false,
|
|
779
|
+
} = opts;
|
|
780
|
+
|
|
781
|
+
const hist = historyKey !== false ? _histLoad(String(historyKey)) : [];
|
|
782
|
+
const hlFn = highlight === false ? null
|
|
783
|
+
: typeof highlight === 'function' ? highlight
|
|
784
|
+
: SyntaxHighlight.get(String(highlight));
|
|
785
|
+
|
|
786
|
+
const maskChar = password ? String(mask) : null;
|
|
787
|
+
const result = _syncReadLine(message, { mask: maskChar, history: hist, highlight: hlFn, placeholder });
|
|
788
|
+
|
|
789
|
+
if (historyKey !== false && !password) _histPush(String(historyKey), result);
|
|
790
|
+
|
|
791
|
+
if (password && hash) return _hashPassword(result, typeof hash === 'string' ? hash : 'sha256');
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ── promptAsync (async version of prompt) ──────────────────────────────
|
|
796
|
+
async function promptAsync(message = '> ', opts = {}) {
|
|
797
|
+
// Runs prompt in a microtask so it doesn't block the event-loop scheduler
|
|
798
|
+
// (on Node the synchronous stdin.read inside _syncReadLine is still
|
|
799
|
+
// blocking, but wrapping in a Promise lets callers await it naturally)
|
|
800
|
+
return new Promise((resolve) => {
|
|
801
|
+
setImmediate(() => resolve(prompt(message, opts)));
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ── promptPassword (sync convenience) ──────────────────────────────────
|
|
806
|
+
function promptPassword(message = 'Password: ', algo = 'sha256') {
|
|
807
|
+
return prompt(message, { password: true, hash: algo });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ── promptPasswordAsync ─────────────────────────────────────────────────
|
|
811
|
+
async function promptPasswordAsync(message = 'Password: ', algo = 'sha256') {
|
|
812
|
+
return promptAsync(message, { password: true, hash: algo });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ── repl (sync) ─────────────────────────────────────────────────────────
|
|
816
|
+
//
|
|
817
|
+
// opts:
|
|
818
|
+
// prompt {string} — prompt string (default '> ')
|
|
819
|
+
// banner {string} — printed before loop starts
|
|
820
|
+
// historyKey {string} — history key (default 'repl')
|
|
821
|
+
// highlight {string|fn|false}
|
|
822
|
+
// eval {fn} — handler(line, ctx) → string|undefined
|
|
823
|
+
// return '__exit__' to quit
|
|
824
|
+
// exit {string[]} — words that quit the repl (default ['exit','quit','.q'])
|
|
825
|
+
// context {object} — arbitrary context passed to eval
|
|
826
|
+
// maxHistory {number} — max history size (default 500)
|
|
827
|
+
//
|
|
828
|
+
function repl(opts = {}) {
|
|
829
|
+
const {
|
|
830
|
+
prompt: promptStr = '> ',
|
|
831
|
+
banner: bannerStr = '',
|
|
832
|
+
historyKey: hk = 'repl',
|
|
833
|
+
highlight: hl = false,
|
|
834
|
+
eval: evalFn = null,
|
|
835
|
+
exit: exitWords = ['exit', 'quit', '.q'],
|
|
836
|
+
context: ctx = {},
|
|
837
|
+
maxHistory: _maxHist = 500,
|
|
838
|
+
} = opts;
|
|
839
|
+
|
|
840
|
+
if (bannerStr) process.stdout.write(bannerStr + '\n');
|
|
841
|
+
|
|
842
|
+
const hlFn = hl === false ? null
|
|
843
|
+
: typeof hl === 'function' ? hl
|
|
844
|
+
: SyntaxHighlight.get(String(hl));
|
|
845
|
+
|
|
846
|
+
while (true) {
|
|
847
|
+
const line = prompt(promptStr, { historyKey: hk, highlight: hlFn });
|
|
848
|
+
|
|
849
|
+
if (line === null) break; // EOF / Ctrl-D
|
|
850
|
+
const trimmed = line.trim();
|
|
851
|
+
if (!trimmed) continue;
|
|
852
|
+
if (exitWords.includes(trimmed.toLowerCase())) break;
|
|
853
|
+
|
|
854
|
+
let out;
|
|
855
|
+
if (evalFn) {
|
|
856
|
+
try { out = evalFn(trimmed, ctx); }
|
|
857
|
+
catch (e) { out = `\x1b[31mError: ${e && e.message ? e.message : e}\x1b[0m`; }
|
|
858
|
+
}
|
|
859
|
+
if (out === '__exit__') break;
|
|
860
|
+
if (out !== undefined && out !== null) process.stdout.write(String(out) + '\n');
|
|
546
861
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ── replAsync ───────────────────────────────────────────────────────────
|
|
865
|
+
async function replAsync(opts = {}) {
|
|
866
|
+
const {
|
|
867
|
+
prompt: promptStr = '> ',
|
|
868
|
+
banner: bannerStr = '',
|
|
869
|
+
historyKey: hk = 'repl',
|
|
870
|
+
highlight: hl = false,
|
|
871
|
+
eval: evalFn = null,
|
|
872
|
+
exit: exitWords = ['exit', 'quit', '.q'],
|
|
873
|
+
context: ctx = {},
|
|
874
|
+
} = opts;
|
|
875
|
+
|
|
876
|
+
if (bannerStr) process.stdout.write(bannerStr + '\n');
|
|
877
|
+
|
|
878
|
+
const hlFn = hl === false ? null
|
|
879
|
+
: typeof hl === 'function' ? hl
|
|
880
|
+
: SyntaxHighlight.get(String(hl));
|
|
881
|
+
|
|
882
|
+
while (true) {
|
|
883
|
+
const line = await promptAsync(promptStr, { historyKey: hk, highlight: hlFn });
|
|
884
|
+
|
|
885
|
+
if (line === null) break;
|
|
886
|
+
const trimmed = line.trim();
|
|
887
|
+
if (!trimmed) continue;
|
|
888
|
+
if (exitWords.includes(trimmed.toLowerCase())) break;
|
|
889
|
+
|
|
890
|
+
let out;
|
|
891
|
+
if (evalFn) {
|
|
550
892
|
try {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
893
|
+
const raw = evalFn(trimmed, ctx);
|
|
894
|
+
out = raw instanceof Promise ? await raw : raw;
|
|
895
|
+
} catch (e) {
|
|
896
|
+
out = `\x1b[31mError: ${e && e.message ? e.message : e}\x1b[0m`;
|
|
554
897
|
}
|
|
555
|
-
}
|
|
556
|
-
break;
|
|
898
|
+
}
|
|
899
|
+
if (out === '__exit__') break;
|
|
900
|
+
if (out !== undefined && out !== null) process.stdout.write(String(out) + '\n');
|
|
557
901
|
}
|
|
558
902
|
}
|
|
559
|
-
|
|
903
|
+
|
|
904
|
+
// ── mouse — terminal mouse / click events ───────────────────────────────
|
|
905
|
+
//
|
|
906
|
+
// Enables SGR mouse reporting and calls cb({ type, button, x, y, ... })
|
|
907
|
+
// for every mouse event. Returns a stop() function.
|
|
908
|
+
//
|
|
909
|
+
// opts:
|
|
910
|
+
// buttons {boolean} — report button press/release (default true)
|
|
911
|
+
// motion {boolean} — report mouse motion (default false)
|
|
912
|
+
// scroll {boolean} — report scroll events (default true)
|
|
913
|
+
//
|
|
914
|
+
function mouse(cb, opts = {}) {
|
|
915
|
+
const { buttons = true, motion = false, scroll = true } = opts;
|
|
916
|
+
|
|
917
|
+
const stdin = process.stdin;
|
|
918
|
+
const stdout = process.stdout;
|
|
919
|
+
|
|
920
|
+
// Enable mouse
|
|
921
|
+
stdout.write(_ansi.mouseOn);
|
|
922
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
923
|
+
stdin.resume();
|
|
924
|
+
|
|
925
|
+
function onData(raw) {
|
|
926
|
+
// SGR mouse: ESC [ < Pb ; Px ; Py M/m
|
|
927
|
+
const sgr = raw.toString().match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
928
|
+
if (sgr) {
|
|
929
|
+
const btn = parseInt(sgr[1], 10);
|
|
930
|
+
const x = parseInt(sgr[2], 10) - 1;
|
|
931
|
+
const y = parseInt(sgr[3], 10) - 1;
|
|
932
|
+
const release = sgr[4] === 'm';
|
|
933
|
+
const button = btn & 3;
|
|
934
|
+
const isScroll = btn >= 64;
|
|
935
|
+
const isMotion = !!(btn & 32);
|
|
936
|
+
|
|
937
|
+
if (!scroll && isScroll) return;
|
|
938
|
+
if (!motion && isMotion && !release) return;
|
|
939
|
+
if (!buttons && !isScroll && !isMotion) return;
|
|
940
|
+
|
|
941
|
+
cb({
|
|
942
|
+
type: release ? 'mouseup' : (isScroll ? 'scroll' : (isMotion ? 'mousemove' : 'mousedown')),
|
|
943
|
+
button,
|
|
944
|
+
x, y,
|
|
945
|
+
shift: !!(btn & 4),
|
|
946
|
+
alt: !!(btn & 8),
|
|
947
|
+
ctrl: !!(btn & 16),
|
|
948
|
+
direction: isScroll ? ((btn & 1) ? 'down' : 'up') : null,
|
|
949
|
+
raw: { btn, x, y, release },
|
|
950
|
+
});
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Ctrl-C passthrough so user can still quit
|
|
955
|
+
if (raw.includes('\x03')) {
|
|
956
|
+
stop();
|
|
957
|
+
stdout.write('\n');
|
|
958
|
+
process.exit(130);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
stdin.on('data', onData);
|
|
963
|
+
|
|
964
|
+
function stop() {
|
|
965
|
+
stdout.write(_ansi.mouseOff);
|
|
966
|
+
stdin.removeListener('data', onData);
|
|
967
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
968
|
+
stdin.pause();
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return stop;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ── history helpers exposed to Nova scripts ─────────────────────────────
|
|
975
|
+
const historyAPI = {
|
|
976
|
+
/** Load history array for a given key */
|
|
977
|
+
load: (name) => _histLoad(String(name)),
|
|
978
|
+
/** Save a history array for a given key */
|
|
979
|
+
save: (name, arr) => _histSave(String(name), arr),
|
|
980
|
+
/** Push a single entry to a history key */
|
|
981
|
+
push: (name, entry) => _histPush(String(name), String(entry)),
|
|
982
|
+
/** Clear history for a key */
|
|
983
|
+
clear: (name) => _histSave(String(name), []),
|
|
984
|
+
/** List all history keys managed by InputMgr */
|
|
985
|
+
keys: () => {
|
|
986
|
+
try {
|
|
987
|
+
return fs.readdirSync('.').filter(f => f.startsWith('.184.inputmgr.'))
|
|
988
|
+
.map(f => f.slice('.184.inputmgr.'.length));
|
|
989
|
+
} catch (_) { return []; }
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
// ── Sync API (primary) ────────────────────────────────────────────────
|
|
995
|
+
prompt,
|
|
996
|
+
promptPassword,
|
|
997
|
+
repl,
|
|
998
|
+
|
|
999
|
+
// ── Async API ─────────────────────────────────────────────────────────
|
|
1000
|
+
promptAsync,
|
|
1001
|
+
promptPasswordAsync,
|
|
1002
|
+
replAsync,
|
|
1003
|
+
|
|
1004
|
+
// ── Mouse / terminal events ────────────────────────────────────────────
|
|
1005
|
+
mouse,
|
|
1006
|
+
|
|
1007
|
+
// ── Syntax highlighting ────────────────────────────────────────────────
|
|
1008
|
+
SyntaxHighlight,
|
|
1009
|
+
highlight: SyntaxHighlight.highlight.bind(SyntaxHighlight),
|
|
1010
|
+
|
|
1011
|
+
// ── Password hashing utility (standalone) ─────────────────────────────
|
|
1012
|
+
hashPassword: _hashPassword,
|
|
1013
|
+
|
|
1014
|
+
// ── History management ────────────────────────────────────────────────
|
|
1015
|
+
history: historyAPI,
|
|
1016
|
+
};
|
|
1017
|
+
})();
|
|
1018
|
+
|
|
1019
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
1020
|
+
// Module exports
|
|
1021
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
1022
|
+
module.exports = {
|
|
1023
|
+
// ── Original exports (all preserved) ──────────────────────────────────
|
|
1024
|
+
Air, Chalk, History,
|
|
1025
|
+
Shema, ShemaType,
|
|
1026
|
+
CustomError, formatError, NovaException,
|
|
1027
|
+
ConfigFile,
|
|
1028
|
+
JsDB, Storage, Zip, Crypto,
|
|
1029
|
+
PathUtils, Base64,
|
|
1030
|
+
MED: MathExprDebugger,
|
|
1031
|
+
|
|
1032
|
+
// ── New: InputMgr + SyntaxHighlight ───────────────────────────────────
|
|
1033
|
+
InputMgr,
|
|
1034
|
+
SyntaxHighlight,
|
|
1035
|
+
};
|