1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
class JSConsoleModule {
constructor() {
this.commands = {};
this.commandsMeta = { 'js': { argType: 'file' }, 'js-run': { argType: 'file' } };
this.terminal = null;
}
getHelpInfo() {
return {
'js': 'js <expression> — Evaluate JavaScript in page context',
'js-run': 'js-run <path> — Load and execute a JS file from VFS',
'node': 'node <path> — Alias for js-run (executes a JS file from VFS)'
};
}
registerCommands() {
// rawText is the untokenised line from the dispatcher (v4.6.130): `js "abc".toUpperCase()`
// must reach eval with its quotes, and _parseArgs deletes them.
this.commands['js'] = (args, rawText) => this.evalJS(args, rawText);
this.commands['js-run'] = (args) => this.evalJSFile(args);
this.commands['node'] = (args) => {
// Bridge: AI hallucinates `node script.js` — map to js-run
if (!args[0]) return this.terminal.print('Usage: node <script.js> (alias for js-run)', 'warning');
// Handle `node -e "expr"` — inline eval like Node.js. No rawText here on purpose:
// _parseArgs keeps `'…'` inside `"…"` verbatim, so `args` already carries the
// expression, whereas the raw form would hand eval `"import('fs')"` (a string
// literal) instead of `import('fs')` (an import). Measured v4.6.130.
if (args[0] === '-e') {
return this.evalJS(args.slice(1));
}
return this.evalJSFile(args);
};
}
// The expression to evaluate: the raw remainder when the terminal provided one,
// otherwise the tokenised arguments (AI tool calls, script runner, direct calls).
_expr(args, rawText) {
if (typeof rawText === 'string') {
const rest = rawText.trim().replace(/^\S+\s*/, ''); // drop the command word
if (rest) return rest;
}
return (args || []).join(' ');
}
evalJS(args, rawText) {
if (!args[0]) return this.terminal.print('Usage: js <expression>', 'warning');
const expr = this._expr(args, rawText);
// v4.6.128: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc.
try {
const result = (function() {
'use strict';
// eslint-disable-next-line no-eval
return eval(expr);
})();
// A thenable must be awaited: an uncaught `import('fs')` rejection escapes to
// window.onunhandledrejection and is auto-filed as a runtime bug report.
if (result && typeof result.then === 'function') {
result.then(val => {
const output = val === undefined ? 'undefined' :
typeof val === 'string' ? val :
val === null ? 'null' :
typeof val === 'object' ? JSON.stringify(val, null, 2) :
String(val);
this.terminal.print(output, 'success');
}).catch(e => {
this._printError(e);
});
return;
}
const output = result === undefined ? 'undefined' :
typeof result === 'string' ? result :
result === null ? 'null' :
typeof result === 'object' ? JSON.stringify(result, null, 2) :
String(result);
this.terminal.print(output, 'success');
} catch (e) {
this._printError(e);
}
}
// Single error sink: prints `Error: <msg>`, plus a Node-builtin hint when the browser
// could not resolve a bare specifier (v4.6.128).
_printError(e) {
const msg = (e && e.message) ? e.message : String(e);
this.terminal.print(`Error: ${msg}`, 'error');
const m = /(?:Failed to resolve module specifier|Cannot find module) '([^']+)'/.exec(msg)
|| /No such built-in module: ?([\w@/.:-]+)/.exec(msg);
if (m) {
this.terminal.print(`'${m[1]}' is a Node built-in; the browser has no Node — ` +
`use the VFS (ls/cat/run, terminal.fs) instead`, 'warning');
}
}
evalJSFile(args) {
if (!args[0]) return this.terminal.print('Usage: js-run <path>', 'warning');
const path = args.join(' ');
try {
const content = this.terminal.fs.readFile(path);
if (content === null || content === undefined || content === false) {
return this.terminal.print(`js-run: ${path}: No such file`, 'error');
}
// v4.5.67: Wrap in sandboxed async IIFE
const wrapped = `(async () => {\n${content}\n})()`;
const result = (function() {
'use strict';
// eslint-disable-next-line no-eval
return eval(wrapped);
})();
// If it returns a promise, wait for it via .then
if (result && typeof result.then === 'function') {
result.then(val => {
const output = val === undefined ? '✓ Script executed' :
typeof val === 'string' ? val :
val === null ? 'null' :
typeof val === 'object' ? JSON.stringify(val, null, 2) :
String(val);
this.terminal.print(output, 'success');
}).catch(e => {
this._printError(e);
});
return;
}
const output = result === undefined ? '✓ Script executed' :
typeof result === 'string' ? result :
result === null ? 'null' :
typeof result === 'object' ? JSON.stringify(result, null, 2) :
String(result);
this.terminal.print(output, 'success');
} catch (e) {
this._printError(e);
}
}
seed() {}
init() {}
}
if (typeof window !== 'undefined') window.JSConsoleModule = JSConsoleModule;
|