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.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -0,0 +1,1035 @@
1
+ const { CustomError, formatError, NovaException } = require('./error.js');
2
+ const { hash } = require('crypto');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const readline = require('readline');
7
+ const { execFileSync, execSync } = require('child_process');
8
+
9
+ let Air = {};
10
+
11
+ // read all .js files in the current directory
12
+ fs.readdirSync(__dirname)
13
+ .filter(file => file !== 'bstd.js' && file.endsWith('.js'))
14
+ .forEach(file => {
15
+ const name = path.basename(file, '.js');
16
+ Air[name] = require(path.join(__dirname, file));
17
+ });
18
+
19
+ let Chalk = require('chalk').default;
20
+
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+ // History (unchanged from original)
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ let History = {
25
+ getKey(name) { return '.184.' + name; },
26
+ exists(key) { return fs.existsSync(key); },
27
+ createHistory(key) {
28
+ fs.writeFileSync(key, JSON.stringify({ history: [] }));
29
+ return key;
30
+ },
31
+ appendHistory(key, addition, pushOrIndex, index) {
32
+ let content = JSON.parse(fs.readFileSync(key));
33
+ if (pushOrIndex === 'index') content.history[index] = addition;
34
+ else content.history.push(addition);
35
+ fs.writeFileSync(key, JSON.stringify(content));
36
+ },
37
+ readHistory(key) {
38
+ let content = JSON.parse(fs.readFileSync(key));
39
+ return content.history;
40
+ },
41
+ rawSetHistory(key, historyArray) {
42
+ let content = JSON.parse(fs.readFileSync(key));
43
+ content.history = historyArray;
44
+ fs.writeFileSync(key, JSON.stringify(content));
45
+ },
46
+ rawGetHistory(key) {
47
+ let content = JSON.parse(fs.readFileSync(key));
48
+ return content.history;
49
+ },
50
+ clearHistory(key) {
51
+ fs.writeFileSync(key, JSON.stringify({ history: [] }));
52
+ }
53
+ };
54
+
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Shema (unchanged from original)
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ const ShemaTypes = {};
59
+ class ShemaType {
60
+ constructor(typename, validatorFunction, defaultObj) {
61
+ this.typename = typename;
62
+ this.validate = validatorFunction;
63
+ this.defaultObj
64
+ ShemaTypes[typename] = this;
65
+ }
66
+ }
67
+ class Shema {
68
+ constructor(obj) { this.shema = obj; }
69
+ validate(obj) {
70
+ for (const key in this.shema) {
71
+ const type = this.shema[key];
72
+ const value = obj[key];
73
+ if (type instanceof ShemaType) {
74
+ if (!type.validate(value))
75
+ throw new Error(`Validation failed for key "${key}": expected ${type.typename}, got ${typeof value}`);
76
+ } else if (type instanceof Shema) {
77
+ type.validate(value);
78
+ } else {
79
+ throw new Error(`Invalid shema definition for key "${key}"`);
80
+ }
81
+ }
82
+ }
83
+ defaultObj() {
84
+ const obj = {};
85
+ for (const key in this.shema) {
86
+ const type = this.shema[key];
87
+ if (type instanceof ShemaType) {
88
+ obj[key] = type.typename === 'Int' || type.typename === 'Float' ? 0 :
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;
97
+ } else if (type instanceof Shema) {
98
+ obj[key] = type.defaultObj();
99
+ } else {
100
+ throw new Error(`Invalid shema definition for key "${key}"`);
101
+ }
102
+ }
103
+ return obj;
104
+ }
105
+ }
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
+ // ─────────────────────────────────────────────────────────────────────────────
125
+ class ConfigFile {
126
+ constructor(name, type, hiddenFile = false, shema) {
127
+ this.path = hiddenFile ? '.134.' + name + '.' + type : name + '.135.' + type;
128
+ this.type = type;
129
+ this.shema = shema instanceof Shema ? shema : null;
130
+ if (!fs.existsSync(this.path))
131
+ fs.writeFileSync(this.path, this.type === 'json' ? JSON.parse(shema.defaultObj) : '');
132
+ }
133
+ writeFull(configObj) {
134
+ if (this.shema && this.shema instanceof Shema && this.shema.validate(configObj) === false)
135
+ throw new Error('Config validation failed');
136
+ fs.writeFileSync(this.path, JSON.stringify(configObj));
137
+ }
138
+ write(key, value) {
139
+ const config = this.readFull();
140
+ let current = config;
141
+ for (let i = 0; i < key.length - 1; i++) {
142
+ if (typeof current[key[i]] !== 'object' || current[key[i]] === null) current[key[i]] = {};
143
+ current = current[key[i]];
144
+ }
145
+ current[key[key.length - 1]] = value;
146
+ this.writeFull(config);
147
+ }
148
+ readFull() {
149
+ const content = fs.readFileSync(this.path, 'utf-8');
150
+ if (this.type === 'json') return JSON.parse(content);
151
+ throw new Error('Unsupported config type: ' + this.type);
152
+ }
153
+ read(key) {
154
+ const config = this.readFull();
155
+ let current = config;
156
+ for (let i = 0; i < key.length; i++) {
157
+ if (typeof current[key[i]] !== 'object' || current[key[i]] === null) return undefined;
158
+ current = current[key[i]];
159
+ }
160
+ return current;
161
+ }
162
+ interactiveSetup(defaultConfig = {}) {
163
+ const config = this.readFull();
164
+ const mergedConfig = { ...defaultConfig, ...config };
165
+ const questions = [];
166
+ const buildQuestions = (obj, path = []) => {
167
+ for (const key in obj) {
168
+ const value = obj[key];
169
+ if (typeof value === 'object' && value !== null) buildQuestions(value, path.concat(key));
170
+ else questions.push({ path: path.concat(key), default: value });
171
+ }
172
+ };
173
+ buildQuestions(mergedConfig);
174
+ const askQuestion = (index) => {
175
+ if (index >= questions.length) { this.writeFull(mergedConfig); console.log('Config saved successfully!'); return; }
176
+ const { path, default: defaultValue } = questions[index];
177
+ InputMgr.prompt(`Enter value for ${path.join('.')} (default: ${defaultValue}): `).then(answer => {
178
+ if (answer.trim() !== '') {
179
+ let current = mergedConfig;
180
+ for (let i = 0; i < path.length - 1; i++) {
181
+ if (typeof current[path[i]] !== 'object' || current[path[i]] === null) current[path[i]] = {};
182
+ current = current[path[i]];
183
+ }
184
+ current[path[path.length - 1]] = answer;
185
+ }
186
+ askQuestion(index + 1);
187
+ });
188
+ };
189
+ askQuestion(0);
190
+ }
191
+ }
192
+
193
+ // ─────────────────────────────────────────────────────────────────────────────
194
+ // JsDB / Storage / Zip / Crypto / PathUtils / Base64 / OsUtils (unchanged)
195
+ // ─────────────────────────────────────────────────────────────────────────────
196
+ let JsDB = {
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,
201
+ gt: globalThis,
202
+ fetch(url, options) {
203
+ const { execSync } = require('child_process');
204
+ let command;
205
+ if (process.platform === 'win32') {
206
+ command = `powershell -Command "Invoke-WebRequest -Uri '${url}' -Method ${options?.method || 'GET'} -Headers @(${Object.entries(options?.headers || {}).map(([k, v]) => `@{${k}='${v}'}`).join(',')}) -Body '${options?.body || ''}' -UseBasicParsing"`;
207
+ } else {
208
+ command = `curl -X ${options?.method || 'GET'} '${url}' ${Object.entries(options?.headers || {}).map(([k, v]) => `-H "${k}: ${v}"`).join(' ')} -d '${options?.body || ''}'`;
209
+ }
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
+
219
+ const Storage = {
220
+ setItem(key, value) {
221
+ const filePath = path.join(__dirname, '.storage', key);
222
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
223
+ fs.writeFileSync(filePath, JSON.stringify(value));
224
+ },
225
+ getItem(key) {
226
+ const filePath = path.join(__dirname, '.storage', key);
227
+ if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
228
+ return null;
229
+ },
230
+ removeItem(key) {
231
+ const filePath = path.join(__dirname, '.storage', key);
232
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
233
+ },
234
+ clear() {
235
+ const storageDir = path.join(__dirname, '.storage');
236
+ if (fs.existsSync(storageDir)) fs.rmSync(storageDir, { recursive: true, force: true });
237
+ }
238
+ };
239
+
240
+ const Zip = {
241
+ zip(files, output) {
242
+ const archiver = require('archiver');
243
+ const outputStream = fs.createWriteStream(output);
244
+ const archive = archiver('zip', { zlib: { level: 9 } });
245
+ return new Promise((resolve, reject) => {
246
+ outputStream.on('close', () => resolve());
247
+ archive.on('error', err => reject(err));
248
+ archive.pipe(outputStream);
249
+ files.forEach(file => { if (fs.existsSync(file)) archive.file(file, { name: path.basename(file) }); });
250
+ archive.finalize();
251
+ });
252
+ },
253
+ unzip(zipFile, outputDir) {
254
+ const unzipper = require('unzipper');
255
+ return new Promise((resolve, reject) => {
256
+ fs.createReadStream(zipFile)
257
+ .pipe(unzipper.Extract({ path: outputDir }))
258
+ .on('close', () => resolve())
259
+ .on('error', err => reject(err));
260
+ });
261
+ }
262
+ };
263
+
264
+ const Crypto = {
265
+ hash(data, algorithm = 'sha256') { return hash(algorithm).update(data).digest('hex'); }
266
+ };
267
+
268
+ const PathUtils = {
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); }
274
+ };
275
+
276
+ const Base64 = {
277
+ encode(str) { return Buffer.from(str, 'utf-8').toString('base64'); },
278
+ decode(encoded) { return Buffer.from(encoded, 'base64').toString('utf-8'); }
279
+ };
280
+
281
+ const OsUtils = {
282
+ platform: process.platform,
283
+ homedir: require('os').homedir,
284
+ tmpdir: require('os').tmpdir,
285
+ cpus: require('os').cpus,
286
+ totalmem: require('os').totalmem,
287
+ freemem: require('os').freemem,
288
+ uptime: require('os').uptime
289
+ };
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})`)
330
+ }
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');
861
+ }
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) {
892
+ try {
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`;
897
+ }
898
+ }
899
+ if (out === '__exit__') break;
900
+ if (out !== undefined && out !== null) process.stdout.write(String(out) + '\n');
901
+ }
902
+ }
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
+ };