@ebowwa/sandbox 0.1.1

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 (108) hide show
  1. package/dist/compilers/index.d.ts +24 -0
  2. package/dist/compilers/index.d.ts.map +1 -0
  3. package/dist/compilers/index.js +42 -0
  4. package/dist/compilers/index.js.map +1 -0
  5. package/dist/compilers/javascript.d.ts +117 -0
  6. package/dist/compilers/javascript.d.ts.map +1 -0
  7. package/dist/compilers/javascript.js +462 -0
  8. package/dist/compilers/javascript.js.map +1 -0
  9. package/dist/compilers/python.d.ts +140 -0
  10. package/dist/compilers/python.d.ts.map +1 -0
  11. package/dist/compilers/python.js +650 -0
  12. package/dist/compilers/python.js.map +1 -0
  13. package/dist/compilers/typescript.d.ts +99 -0
  14. package/dist/compilers/typescript.d.ts.map +1 -0
  15. package/dist/compilers/typescript.js +323 -0
  16. package/dist/compilers/typescript.js.map +1 -0
  17. package/dist/core/cell.d.ts +160 -0
  18. package/dist/core/cell.d.ts.map +1 -0
  19. package/dist/core/cell.js +319 -0
  20. package/dist/core/cell.js.map +1 -0
  21. package/dist/core/compiler.d.ts +126 -0
  22. package/dist/core/compiler.d.ts.map +1 -0
  23. package/dist/core/compiler.js +123 -0
  24. package/dist/core/compiler.js.map +1 -0
  25. package/dist/core/index.d.ts +19 -0
  26. package/dist/core/index.d.ts.map +1 -0
  27. package/dist/core/index.js +14 -0
  28. package/dist/core/index.js.map +1 -0
  29. package/dist/core/limits.d.ts +173 -0
  30. package/dist/core/limits.d.ts.map +1 -0
  31. package/dist/core/limits.js +440 -0
  32. package/dist/core/limits.js.map +1 -0
  33. package/dist/core/permissions.d.ts +103 -0
  34. package/dist/core/permissions.d.ts.map +1 -0
  35. package/dist/core/permissions.js +341 -0
  36. package/dist/core/permissions.js.map +1 -0
  37. package/dist/core/runtime.d.ts +127 -0
  38. package/dist/core/runtime.d.ts.map +1 -0
  39. package/dist/core/runtime.js +325 -0
  40. package/dist/core/runtime.js.map +1 -0
  41. package/dist/core/types.d.ts +380 -0
  42. package/dist/core/types.d.ts.map +1 -0
  43. package/dist/core/types.js +67 -0
  44. package/dist/core/types.js.map +1 -0
  45. package/dist/index.d.ts +145 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +279 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/multi/index.d.ts +9 -0
  50. package/dist/multi/index.d.ts.map +1 -0
  51. package/dist/multi/index.js +7 -0
  52. package/dist/multi/index.js.map +1 -0
  53. package/dist/multi/polyglot.d.ts +179 -0
  54. package/dist/multi/polyglot.d.ts.map +1 -0
  55. package/dist/multi/polyglot.js +319 -0
  56. package/dist/multi/polyglot.js.map +1 -0
  57. package/dist/runtimes/docker.d.ts +97 -0
  58. package/dist/runtimes/docker.d.ts.map +1 -0
  59. package/dist/runtimes/docker.js +368 -0
  60. package/dist/runtimes/docker.js.map +1 -0
  61. package/dist/runtimes/index.d.ts +11 -0
  62. package/dist/runtimes/index.d.ts.map +1 -0
  63. package/dist/runtimes/index.js +9 -0
  64. package/dist/runtimes/index.js.map +1 -0
  65. package/dist/runtimes/process.d.ts +47 -0
  66. package/dist/runtimes/process.d.ts.map +1 -0
  67. package/dist/runtimes/process.js +230 -0
  68. package/dist/runtimes/process.js.map +1 -0
  69. package/dist/session/index.d.ts +12 -0
  70. package/dist/session/index.d.ts.map +1 -0
  71. package/dist/session/index.js +9 -0
  72. package/dist/session/index.js.map +1 -0
  73. package/dist/session/kernel.d.ts +199 -0
  74. package/dist/session/kernel.d.ts.map +1 -0
  75. package/dist/session/kernel.js +400 -0
  76. package/dist/session/kernel.js.map +1 -0
  77. package/dist/session/notebook.d.ts +168 -0
  78. package/dist/session/notebook.d.ts.map +1 -0
  79. package/dist/session/notebook.js +499 -0
  80. package/dist/session/notebook.js.map +1 -0
  81. package/dist/session/repl.d.ts +159 -0
  82. package/dist/session/repl.d.ts.map +1 -0
  83. package/dist/session/repl.js +409 -0
  84. package/dist/session/repl.js.map +1 -0
  85. package/package.json +142 -0
  86. package/src/compilers/index.ts +80 -0
  87. package/src/compilers/javascript.ts +571 -0
  88. package/src/compilers/python.ts +785 -0
  89. package/src/compilers/typescript.ts +442 -0
  90. package/src/core/cell.ts +439 -0
  91. package/src/core/compiler.ts +250 -0
  92. package/src/core/index.ts +123 -0
  93. package/src/core/limits.ts +508 -0
  94. package/src/core/permissions.ts +409 -0
  95. package/src/core/runtime.ts +499 -0
  96. package/src/core/types.ts +528 -0
  97. package/src/global.d.ts +59 -0
  98. package/src/index.ts +515 -0
  99. package/src/multi/index.ts +22 -0
  100. package/src/multi/polyglot.ts +461 -0
  101. package/src/runtimes/docker.ts +501 -0
  102. package/src/runtimes/index.ts +21 -0
  103. package/src/runtimes/process.ts +316 -0
  104. package/src/session/index.ts +41 -0
  105. package/src/session/kernel.ts +553 -0
  106. package/src/session/notebook.ts +635 -0
  107. package/src/session/repl.ts +521 -0
  108. package/src/wasm2wasm.d.ts +35 -0
@@ -0,0 +1,521 @@
1
+ /**
2
+ * REPL Primitive
3
+ *
4
+ * Interactive Read-Eval-Print-Loop for multi-language execution.
5
+ * Provides terminal-style interactive coding experience.
6
+ */
7
+
8
+ import type {
9
+ Language,
10
+ ExecutionResult,
11
+ Permissions,
12
+ Limits,
13
+ } from "../core/types.js";
14
+ import { BaseKernel, KernelManager } from "./kernel.js";
15
+ import { createPermissions } from "../core/permissions.js";
16
+ import { LimitsParser } from "../core/limits.js";
17
+ import * as readline from "node:readline/promises";
18
+ import { stdin as input, stdout as output } from "node:process";
19
+
20
+ /**
21
+ * REPL options
22
+ */
23
+ export interface REPLOptions {
24
+ /** Default language */
25
+ language?: Language;
26
+ /** Available languages */
27
+ languages?: Language[];
28
+ /** Permissions */
29
+ permissions?: Permissions;
30
+ /** Resource limits */
31
+ limits?: Limits;
32
+ /** Prompt string */
33
+ prompt?: string;
34
+ /** Continuation prompt */
35
+ continuationPrompt?: string;
36
+ /** Welcome message */
37
+ welcomeMessage?: string;
38
+ /** Kernel manager */
39
+ kernelManager?: KernelManager;
40
+ /** Kernel factory */
41
+ kernelFactory?: (language: Language) => Promise<BaseKernel>;
42
+ /** Output stream */
43
+ output?: NodeJS.WritableStream;
44
+ /** Input stream */
45
+ input?: NodeJS.ReadableStream;
46
+ }
47
+
48
+ /**
49
+ * REPL history entry
50
+ */
51
+ export interface REPLHistoryEntry {
52
+ /** Input code */
53
+ input: string;
54
+ /** Language */
55
+ language: Language;
56
+ /** Execution result */
57
+ result: ExecutionResult;
58
+ /** Timestamp */
59
+ timestamp: Date;
60
+ }
61
+
62
+ /**
63
+ * REPL state
64
+ */
65
+ export interface REPLState {
66
+ /** Current language */
67
+ language: Language;
68
+ /** Is reading multiline input */
69
+ inMultiline: boolean;
70
+ /** Current multiline buffer */
71
+ multilineBuffer: string[];
72
+ /** History */
73
+ history: REPLHistoryEntry[];
74
+ /** Variables */
75
+ variables: Map<string, unknown>;
76
+ }
77
+
78
+ /**
79
+ * Base REPL Implementation
80
+ */
81
+ export class BaseREPL {
82
+ private kernelManager: KernelManager;
83
+ private kernelFactory?: (language: Language) => Promise<BaseKernel>;
84
+ private kernels = new Map<Language, BaseKernel>();
85
+ private permissions: Permissions;
86
+ private limits: Limits;
87
+ private prompt: string;
88
+ private continuationPrompt: string;
89
+ private welcomeMessage: string;
90
+ private languages: Language[];
91
+ private output: NodeJS.WritableStream;
92
+ private input: NodeJS.ReadableStream;
93
+ private rl?: readline.Interface;
94
+
95
+ state: REPLState;
96
+
97
+ constructor(options: REPLOptions = {}) {
98
+ this.kernelManager = options.kernelManager ?? new KernelManager();
99
+ this.kernelFactory = options.kernelFactory;
100
+ this.permissions = options.permissions ?? {};
101
+ this.limits = options.limits ?? {};
102
+ this.prompt = options.prompt ?? ">>> ";
103
+ this.continuationPrompt = options.continuationPrompt ?? "... ";
104
+ this.welcomeMessage = options.welcomeMessage ?? this.getDefaultWelcome();
105
+ this.languages = options.languages ?? ["javascript", "python", "wasm"];
106
+ this.output = options.output ?? output;
107
+ this.input = options.input ?? input;
108
+
109
+ this.state = {
110
+ language: options.language ?? "javascript",
111
+ inMultiline: false,
112
+ multilineBuffer: [],
113
+ history: [],
114
+ variables: new Map(),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Start REPL
120
+ */
121
+ async start(): Promise<void> {
122
+ this.print(this.welcomeMessage);
123
+ this.print("");
124
+
125
+ this.rl = readline.createInterface({
126
+ input: this.input,
127
+ output: this.output,
128
+ prompt: this.getPrompt(),
129
+ history: this.getHistoryStrings(),
130
+ });
131
+
132
+ this.rl.prompt();
133
+
134
+ for await (const line of this.rl) {
135
+ await this.handleLine(line);
136
+ this.rl.setPrompt(this.getPrompt());
137
+ this.rl.prompt();
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Handle a line of input
143
+ */
144
+ async handleLine(line: string): Promise<void> {
145
+ // Check for commands
146
+ if (line.startsWith("%")) {
147
+ await this.handleCommand(line);
148
+ return;
149
+ }
150
+
151
+ // Check for language switch
152
+ const langMatch = line.match(/^!(\w+)/);
153
+ if (langMatch) {
154
+ const lang = langMatch[1] as Language;
155
+ if (this.languages.includes(lang)) {
156
+ this.state.language = lang;
157
+ this.print(`Switched to ${lang}`);
158
+ } else {
159
+ this.print(`Unknown language: ${lang}`);
160
+ this.print(`Available: ${this.languages.join(", ")}`);
161
+ }
162
+ return;
163
+ }
164
+
165
+ // Check for empty line
166
+ if (line.trim() === "") {
167
+ if (this.state.inMultiline) {
168
+ // Execute multiline
169
+ const code = this.state.multilineBuffer.join("\n");
170
+ this.state.multilineBuffer = [];
171
+ this.state.inMultiline = false;
172
+ await this.execute(code);
173
+ }
174
+ return;
175
+ }
176
+
177
+ // Check for continuation
178
+ if (this.shouldContinue(line)) {
179
+ this.state.inMultiline = true;
180
+ this.state.multilineBuffer.push(line);
181
+ return;
182
+ }
183
+
184
+ // Execute single line
185
+ if (this.state.inMultiline) {
186
+ this.state.multilineBuffer.push(line);
187
+ } else {
188
+ await this.execute(line);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Execute code
194
+ */
195
+ async execute(code: string): Promise<ExecutionResult> {
196
+ const kernel = await this.getKernel(this.state.language);
197
+ const result = await kernel.execute(code);
198
+
199
+ // Add to history
200
+ this.state.history.push({
201
+ input: code,
202
+ language: this.state.language,
203
+ result,
204
+ timestamp: new Date(),
205
+ });
206
+
207
+ // Display result
208
+ this.displayResult(result);
209
+
210
+ // Update variables
211
+ if (result.success && result.exports) {
212
+ for (const [key, value] of result.exports) {
213
+ this.state.variables.set(key, value);
214
+ }
215
+ }
216
+
217
+ return result;
218
+ }
219
+
220
+ /**
221
+ * Handle REPL command
222
+ */
223
+ private async handleCommand(command: string): Promise<void> {
224
+ const [cmd, ...args] = command.slice(1).split(/\s+/);
225
+
226
+ switch (cmd.toLowerCase()) {
227
+ case "help":
228
+ this.showHelp();
229
+ break;
230
+
231
+ case "exit":
232
+ case "quit":
233
+ this.print("Goodbye!");
234
+ process.exit(0);
235
+
236
+ case "clear":
237
+ console.clear();
238
+ break;
239
+
240
+ case "reset":
241
+ await this.reset();
242
+ this.print("Session reset");
243
+ break;
244
+
245
+ case "history":
246
+ this.showHistory();
247
+ break;
248
+
249
+ case "vars":
250
+ this.showVariables();
251
+ break;
252
+
253
+ case "lang":
254
+ case "language":
255
+ if (args[0]) {
256
+ const lang = args[0] as Language;
257
+ if (this.languages.includes(lang)) {
258
+ this.state.language = lang;
259
+ this.print(`Switched to ${lang}`);
260
+ } else {
261
+ this.print(`Unknown language: ${lang}`);
262
+ }
263
+ } else {
264
+ this.print(`Current language: ${this.state.language}`);
265
+ this.print(`Available: ${this.languages.join(", ")}`);
266
+ }
267
+ break;
268
+
269
+ case "save":
270
+ await this.save(args[0] ?? "session.json");
271
+ break;
272
+
273
+ case "load":
274
+ await this.load(args[0] ?? "session.json");
275
+ break;
276
+
277
+ default:
278
+ this.print(`Unknown command: ${cmd}`);
279
+ this.print("Type %help for available commands");
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Get or create kernel for language
285
+ */
286
+ private async getKernel(language: Language): Promise<BaseKernel> {
287
+ let kernel = this.kernels.get(language);
288
+
289
+ if (!kernel) {
290
+ if (this.kernelFactory) {
291
+ kernel = await this.kernelFactory(language);
292
+ } else {
293
+ throw new Error(`No kernel available for language: ${language}`);
294
+ }
295
+
296
+ this.kernels.set(language, kernel);
297
+ }
298
+
299
+ return kernel;
300
+ }
301
+
302
+ /**
303
+ * Check if input should continue to next line
304
+ */
305
+ private shouldContinue(line: string): boolean {
306
+ // Check for incomplete statements
307
+ const openBrackets = (line.match(/[\{\[\(]/g) ?? []).length;
308
+ const closeBrackets = (line.match(/[\}\]\)]/g) ?? []).length;
309
+
310
+ if (openBrackets > closeBrackets) return true;
311
+
312
+ // Check for trailing operators/keywords
313
+ const trimmed = line.trim();
314
+ if (trimmed.endsWith(":") || trimmed.endsWith("\\")) return true;
315
+ if (/^(if|else|for|while|def|function|class)\b/.test(trimmed)) return true;
316
+
317
+ return false;
318
+ }
319
+
320
+ /**
321
+ * Display execution result
322
+ */
323
+ private displayResult(result: ExecutionResult): void {
324
+ // Show stdout
325
+ for (const line of result.output.stdout) {
326
+ this.print(line);
327
+ }
328
+
329
+ // Show stderr
330
+ for (const line of result.output.stderr) {
331
+ this.print(`[stderr] ${line}`);
332
+ }
333
+
334
+ // Show displays
335
+ for (const display of result.output.displays) {
336
+ switch (display.type) {
337
+ case "text":
338
+ this.print(String(display.data));
339
+ break;
340
+ case "html":
341
+ case "image":
342
+ case "json":
343
+ case "markdown":
344
+ this.print(`[${display.type} output]`);
345
+ break;
346
+ }
347
+ }
348
+
349
+ // Show return value
350
+ if (result.success && result.value !== undefined) {
351
+ this.print(this.formatValue(result.value));
352
+ }
353
+
354
+ // Show error
355
+ if (result.error) {
356
+ this.print(`Error (${result.error.type}): ${result.error.message}`);
357
+ if (result.error.stack) {
358
+ this.print(result.error.stack);
359
+ }
360
+ }
361
+
362
+ // Show metrics
363
+ this.print(`[${result.metrics.duration}ms]`);
364
+ }
365
+
366
+ /**
367
+ * Format value for display
368
+ */
369
+ private formatValue(value: unknown): string {
370
+ if (value === undefined) return "undefined";
371
+ if (value === null) return "null";
372
+ if (typeof value === "string") return `"${value}"`;
373
+ if (typeof value === "object") {
374
+ try {
375
+ return JSON.stringify(value, null, 2);
376
+ } catch {
377
+ return String(value);
378
+ }
379
+ }
380
+ return String(value);
381
+ }
382
+
383
+ /**
384
+ * Get prompt string
385
+ */
386
+ private getPrompt(): string {
387
+ if (this.state.inMultiline) {
388
+ return this.continuationPrompt;
389
+ }
390
+ return `[${this.state.language}] ${this.prompt}`;
391
+ }
392
+
393
+ /**
394
+ * Get history as strings
395
+ */
396
+ private getHistoryStrings(): string[] {
397
+ return this.state.history.map(h => h.input);
398
+ }
399
+
400
+ /**
401
+ * Show help
402
+ */
403
+ private showHelp(): void {
404
+ this.print(`
405
+ REPL Commands:
406
+ %help Show this help
407
+ %exit, %quit Exit REPL
408
+ %clear Clear screen
409
+ %reset Reset session
410
+ %history Show execution history
411
+ %vars Show defined variables
412
+ %lang [name] Switch or show language
413
+ %save [file] Save session
414
+ %load [file] Load session
415
+
416
+ Shortcuts:
417
+ !python Switch to Python
418
+ !javascript Switch to JavaScript
419
+ !wasm Switch to WASM
420
+
421
+ Press Enter twice to execute multiline input.
422
+ `);
423
+ }
424
+
425
+ /**
426
+ * Show history
427
+ */
428
+ private showHistory(): void {
429
+ for (let i = 0; i < this.state.history.length; i++) {
430
+ const entry = this.state.history[i];
431
+ this.print(`${i + 1}: [${entry.language}] ${entry.input.slice(0, 50)}...`);
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Show variables
437
+ */
438
+ private showVariables(): void {
439
+ if (this.state.variables.size === 0) {
440
+ this.print("No variables defined");
441
+ return;
442
+ }
443
+
444
+ for (const [key, value] of this.state.variables) {
445
+ this.print(`${key} = ${this.formatValue(value)}`);
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Reset session
451
+ */
452
+ private async reset(): Promise<void> {
453
+ // Restart all kernels
454
+ for (const kernel of this.kernels.values()) {
455
+ await kernel.restart();
456
+ }
457
+
458
+ // Clear state
459
+ this.state.history = [];
460
+ this.state.variables.clear();
461
+ this.state.inMultiline = false;
462
+ this.state.multilineBuffer = [];
463
+ }
464
+
465
+ /**
466
+ * Save session
467
+ */
468
+ private async save(filename: string): Promise<void> {
469
+ const fs = await import("node:fs/promises");
470
+ const data = {
471
+ language: this.state.language,
472
+ history: this.state.history,
473
+ variables: Array.from(this.state.variables.entries()),
474
+ };
475
+
476
+ await fs.writeFile(filename, JSON.stringify(data, null, 2));
477
+ this.print(`Session saved to ${filename}`);
478
+ }
479
+
480
+ /**
481
+ * Load session
482
+ */
483
+ private async load(filename: string): Promise<void> {
484
+ const fs = await import("node:fs/promises");
485
+ const data = JSON.parse(await fs.readFile(filename, "utf-8"));
486
+
487
+ this.state.language = data.language;
488
+ this.state.history = data.history;
489
+ this.state.variables = new Map(data.variables);
490
+
491
+ this.print(`Session loaded from ${filename}`);
492
+ }
493
+
494
+ /**
495
+ * Print to output
496
+ */
497
+ private print(message: string): void {
498
+ this.output.write(message + "\n");
499
+ }
500
+
501
+ /**
502
+ * Get default welcome message
503
+ */
504
+ private getDefaultWelcome(): string {
505
+ return `
506
+ ╔════════════════════════════════════════════════════════════════╗
507
+ ║ @ebowwa/sandbox REPL ║
508
+ ║ ║
509
+ ║ Multi-language sandboxed execution environment ║
510
+ ║ Type %help for commands, !<lang> to switch language ║
511
+ ╚════════════════════════════════════════════════════════════════╝
512
+ `.trim();
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Create a REPL
518
+ */
519
+ export function createREPL(options?: REPLOptions): BaseREPL {
520
+ return new BaseREPL(options);
521
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Type declarations for @ebowwa/wasm2wasm (stub)
3
+ * TODO: Remove when package is published
4
+ */
5
+
6
+ declare module "@ebowwa/wasm2wasm" {
7
+ export interface ValidationResult {
8
+ valid: boolean;
9
+ error?: string;
10
+ }
11
+
12
+ export interface WasmRuntimeOptions {
13
+ maxMemoryPages?: number;
14
+ }
15
+
16
+ export interface ExecutionResult {
17
+ success: boolean;
18
+ output?: unknown;
19
+ error?: string;
20
+ memoryUsed?: number;
21
+ executionTime?: number;
22
+ }
23
+
24
+ export function validate(bytes: Uint8Array): ValidationResult;
25
+ export function execute(
26
+ bytes: Uint8Array,
27
+ options?: WasmRuntimeOptions
28
+ ): Promise<ExecutionResult>;
29
+
30
+ export class WasmRuntimeWrapper {
31
+ constructor(options?: WasmRuntimeOptions);
32
+ execute(bytes: Uint8Array): Promise<ExecutionResult>;
33
+ terminate(): void;
34
+ }
35
+ }