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
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 '.184.' + name;},
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
- return content.history;
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
- return { result: buffer, history };
208
- }
209
- }
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Shema (unchanged from original)
57
+ // ─────────────────────────────────────────────────────────────────────────────
210
58
  const ShemaTypes = {};
211
59
  class ShemaType {
212
- // Shema type detector for the Shema class.
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
- type.typename === 'String' ? '' :
245
- type.typename === 'Boolean' ? false :
246
- type.typename === 'Array' ? [] :
247
- type.typename === 'Object' ? {} :
248
- type.typename === 'Function' ? () => {} :
249
- type.typename === 'Null' ? null :
250
- type.typename === 'Undefined' ? undefined :
251
- null;
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', function (val) {
262
- return Number.isInteger(val);
263
- });
264
- new ShemaType('Float', function (val) {
265
- return typeof val === 'number' && !Number.isNaN(val);
266
- });
267
- new ShemaType('String', function (val) {
268
- return typeof val === 'string';
269
- });
270
- new ShemaType('Boolean', function (val) {
271
- return typeof val === 'boolean';
272
- });
273
- new ShemaType('Array', function (val) {
274
- return Array.isArray(val);
275
- });
276
- new ShemaType('Object', function (val) {
277
- return val !== null && typeof val === 'object' && !Array.isArray(val);
278
- });
279
- new ShemaType('Function', function (val) {
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 /** type: 'json', 'xml' */, hiddenFile = false, shema) {
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) { // key is an array of keys for nested objects, like ['database', 'host']
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
- return JSON.parse(content);
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) { // key is an array of keys for nested objects, like ['database', 'host']
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
- buildQuestions(value, path.concat(key));
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
- Input.prompt(`Enter value for ${path.join('.')} (default: ${defaultValue}): `, { password: false }).result.then(answer => {
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
- return (...args) => new JsClass(...args);
395
- },
396
- run(code) {
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
- const response = execSync(command, { encoding: 'utf-8' });
412
- return response;
413
- } catch (error) {
414
- throw new Error(`Fetch failed: ${error.message}`);
415
- }
416
- },
417
- process,
418
- version: process.version,
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
- return path.join(...segments);
485
- },
486
- resolve(...segments) {
487
- return path.resolve(...segments);
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
- return Buffer.from(str, 'utf-8').toString('base64');
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) { // platform-specific utilities can be added here if needed
517
- case 'win32': {
518
- OsUtils.requestAdminPrivileges = function() {
519
- const { execSync } = require('child_process');
520
- try {
521
- execSync('powershell -Command "Start-Process cmd -Verb runAs"');
522
- } catch (error) {
523
- throw new Error('Failed to request admin privileges: ' + error.message);
524
- }
525
- };
526
- OsUtils.openFile = function(filePath) {
527
- const { execSync } = require('child_process');
528
- try {
529
- execSync(`start "" "${filePath}"`);
530
- } catch (error) {
531
- throw new Error('Failed to open file: ' + error.message);
532
- }
533
- };
534
- break;
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
- case 'darwin': {
537
- OsUtils.openFile = function(filePath) {
538
- const { execSync } = require('child_process');
539
- try {
540
- execSync(`open "${filePath}"`);
541
- } catch (error) {
542
- throw new Error('Failed to open file: ' + error.message);
543
- }
544
- };
545
- break;
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
- case 'linux': {
548
- OsUtils.openFile = function(filePath) {
549
- const { execSync } = require('child_process');
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
- execSync(`xdg-open "${filePath}"`);
552
- } catch (error) {
553
- throw new Error('Failed to open file: ' + error.message);
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
- module.exports = { Air, Chalk, History, Input, Shema, ShemaType, CustomError, formatError, NovaException, ConfigFile, JsDB, Storage, Zip, Crypto, PathUtils, Base64 };
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
+ };