@illuma-ai/code-sandbox 1.3.1 → 1.4.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 (2) hide show
  1. package/README.md +715 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,715 @@
1
+ # @illuma-ai/code-sandbox
2
+
3
+ A browser-native code sandbox with file tree, Monaco editor, terminal, and live preview — powered by [Nodepod](https://www.npmjs.com/package/@illuma-ai/nodepod).
4
+
5
+ Run Express + React applications entirely in the browser. No server-side containers required.
6
+
7
+ ## Features
8
+
9
+ - Full IDE workbench: file tree, code editor, terminal, live preview
10
+ - Runs Node.js in the browser via Nodepod (SharedArrayBuffer + Service Worker)
11
+ - Monaco editor with syntax highlighting, diff viewer, and file tabs
12
+ - Structured error reporting with file path, line number, and source context
13
+ - Hot reload for CSS/HTML/static asset changes (no server restart)
14
+ - Imperative API for programmatic control (designed for AI agent integration)
15
+ - Built-in project templates (Express + React full-stack apps)
16
+ - Customizable via CSS custom properties
17
+ - Dark theme by default, Ranger/shadcn theme variable bridge
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @illuma-ai/code-sandbox
23
+ ```
24
+
25
+ ### Peer Dependencies
26
+
27
+ These must be installed by the consuming application:
28
+
29
+ ```bash
30
+ npm install react react-dom @monaco-editor/react monaco-editor @xterm/xterm @xterm/addon-fit lucide-react
31
+ ```
32
+
33
+ | Peer Dependency | Version |
34
+ | ---------------------- | ---------------------- |
35
+ | `react` | `^18.0.0 \|\| ^19.0.0` |
36
+ | `react-dom` | `^18.0.0 \|\| ^19.0.0` |
37
+ | `@monaco-editor/react` | `^4.6.0` |
38
+ | `monaco-editor` | `>=0.40.0` |
39
+ | `@xterm/xterm` | `^5.5.0 \|\| ^6.0.0` |
40
+ | `@xterm/addon-fit` | `^0.11.0` |
41
+ | `lucide-react` | `>=0.300.0` |
42
+
43
+ ## Prerequisites
44
+
45
+ ### COOP/COEP Headers
46
+
47
+ Nodepod requires `SharedArrayBuffer`, which is only available in cross-origin isolated contexts. Your dev server must send these headers:
48
+
49
+ ```
50
+ Cross-Origin-Opener-Policy: same-origin
51
+ Cross-Origin-Embedder-Policy: credentialless
52
+ ```
53
+
54
+ **Vite example:**
55
+
56
+ ```ts
57
+ // vite.config.ts
58
+ export default defineConfig({
59
+ server: {
60
+ headers: {
61
+ "Cross-Origin-Opener-Policy": "same-origin",
62
+ "Cross-Origin-Embedder-Policy": "credentialless",
63
+ },
64
+ },
65
+ });
66
+ ```
67
+
68
+ ### Service Worker
69
+
70
+ Copy the Nodepod service worker file to your `public/` directory so it's served at `/__sw__.js`:
71
+
72
+ ```bash
73
+ cp node_modules/@illuma-ai/nodepod/dist/sw.js public/__sw__.js
74
+ ```
75
+
76
+ ## Quick Start
77
+
78
+ ```tsx
79
+ import { useRef } from "react";
80
+ import { CodeSandbox } from "@illuma-ai/code-sandbox";
81
+ import type { CodeSandboxHandle } from "@illuma-ai/code-sandbox";
82
+ import "@illuma-ai/code-sandbox/styles.css";
83
+
84
+ function App() {
85
+ const ref = useRef<CodeSandboxHandle>(null);
86
+
87
+ return (
88
+ <CodeSandbox
89
+ ref={ref}
90
+ template="fullstack-starter"
91
+ height="100vh"
92
+ onServerReady={(port, url) => console.log(`Ready on port ${port}`)}
93
+ onSandboxError={(err) => console.error(err.category, err.message)}
94
+ />
95
+ );
96
+ }
97
+ ```
98
+
99
+ ### With Custom Files
100
+
101
+ ```tsx
102
+ const files = {
103
+ "package.json": JSON.stringify(
104
+ {
105
+ name: "my-app",
106
+ dependencies: { express: "^4.18.0" },
107
+ },
108
+ null,
109
+ 2,
110
+ ),
111
+ "server.js": `
112
+ const express = require("express");
113
+ const app = express();
114
+ app.get("/", (req, res) => res.send("<h1>Hello World</h1>"));
115
+ app.listen(3000, () => console.log("Running on port 3000"));
116
+ `,
117
+ };
118
+
119
+ <CodeSandbox
120
+ ref={ref}
121
+ files={files}
122
+ entryCommand="node server.js"
123
+ port={3000}
124
+ height="100vh"
125
+ />;
126
+ ```
127
+
128
+ ### AI Agent Integration
129
+
130
+ The sandbox is designed as a "dumb renderer" — the host application pushes files in and reads state out. The user does not edit code in the sandbox; an AI agent writes code, and the host pushes it via the imperative API.
131
+
132
+ ```tsx
133
+ const ref = useRef<CodeSandboxHandle>(null);
134
+
135
+ // Agent generates new files → push them into the sandbox
136
+ await ref.current.updateFiles(agentGeneratedFiles);
137
+
138
+ // Agent modifies a single file
139
+ await ref.current.updateFile("server.js", newServerCode);
140
+
141
+ // Read errors for the agent to self-correct
142
+ const errors = ref.current.getErrors();
143
+ // errors[0] → { category: "process-stderr", message: "...", filePath: "server.js", line: 42, sourceContext: "..." }
144
+ ```
145
+
146
+ ---
147
+
148
+ ## API Reference
149
+
150
+ ### `<CodeSandbox>` Component
151
+
152
+ The main component. Renders the full workbench (file tree, editor, terminal, preview). Uses `React.forwardRef` to expose the imperative handle.
153
+
154
+ ```tsx
155
+ <CodeSandbox
156
+ ref={sandboxRef}
157
+ files={fileMap} // OR template="fullstack-starter"
158
+ entryCommand="node server.js"
159
+ port={3000}
160
+ env={{ NODE_ENV: "development" }}
161
+ height="100vh"
162
+ className="my-sandbox"
163
+ onFileChange={(path, content) => {}}
164
+ onServerReady={(port, url) => {}}
165
+ onProgress={(progress) => {}}
166
+ onError={(message) => {}}
167
+ onSandboxError={(error) => {}}
168
+ onFilesUpdated={(changes) => {}}
169
+ />
170
+ ```
171
+
172
+ #### Props (`CodeSandboxProps`)
173
+
174
+ | Prop | Type | Default | Description |
175
+ | -------------- | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
176
+ | `files` | `FileMap` | — | Initial file set. Pass a flat `Record<string, string>` mapping file paths to contents. |
177
+ | `template` | `string` | — | Use a built-in template instead of `files`. One of: `"express-react"`, `"fullstack-starter"`. |
178
+ | `entryCommand` | `string` | Inferred from `package.json` or `"node server.js"` | Shell command to start the dev server. |
179
+ | `port` | `number` | `3000` | Port the server listens on. |
180
+ | `env` | `Record<string, string>` | — | Environment variables passed to Node.js processes. |
181
+ | `height` | `string` | `"100vh"` | CSS height of the sandbox root element. |
182
+ | `className` | `string` | — | CSS class name for the root element. |
183
+
184
+ #### Callback Props
185
+
186
+ | Callback | Signature | Description |
187
+ | ---------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
188
+ | `onFileChange` | `(path: string, content: string) => void` | Fires when a file is modified in the editor. |
189
+ | `onServerReady` | `(port: number, url: string) => void` | Fires when the dev server is ready (initial boot or after restart). |
190
+ | `onProgress` | `(progress: BootProgress) => void` | Fires on boot progress changes. |
191
+ | `onError` | `(message: string) => void` | Fires on errors (legacy string format). |
192
+ | `onSandboxError` | `(error: SandboxError) => void` | Fires when a structured error is detected. **Primary error channel for AI agent integration.** |
193
+ | `onFilesUpdated` | `(changes: Record<string, FileChangeStatus>) => void` | Fires after `updateFiles()` completes (files written + server restarted). |
194
+
195
+ ---
196
+
197
+ ### `CodeSandboxHandle` (Imperative API)
198
+
199
+ Access via a React ref. This is the primary API for host applications to control the sandbox programmatically.
200
+
201
+ ```tsx
202
+ const ref = useRef<CodeSandboxHandle>(null);
203
+ <CodeSandbox ref={ref} ... />
204
+ ```
205
+
206
+ #### Methods
207
+
208
+ ##### `updateFiles(files, options?)`
209
+
210
+ Replace the entire file set. Diffs against current files, writes only changed files to the virtual FS, and restarts the server if anything changed.
211
+
212
+ The previous file set becomes `originalFiles` for diff tracking.
213
+
214
+ ```ts
215
+ await ref.current.updateFiles(newFileMap);
216
+ await ref.current.updateFiles(newFileMap, { restartServer: false });
217
+ ```
218
+
219
+ | Param | Type | Description |
220
+ | ----------------------- | --------- | ------------------------------------------------------------- |
221
+ | `files` | `FileMap` | Complete new file set. |
222
+ | `options.restartServer` | `boolean` | Whether to restart the server after writing. Default: `true`. |
223
+
224
+ **Hot reload vs cold restart:** If ALL changed files are hot-reloadable (CSS, HTML, images, fonts), the sandbox refreshes the preview iframe without restarting the Node.js server. If ANY changed file is JS/TS/JSON, the server process is killed and re-run.
225
+
226
+ ##### `updateFile(path, content)`
227
+
228
+ Update a single file. Writes to the virtual FS and updates state. Does **not** restart the server — call `restart()` manually if needed, or use `updateFiles()` for bulk updates with auto-restart.
229
+
230
+ ```ts
231
+ await ref.current.updateFile("public/styles.css", newCss);
232
+ ```
233
+
234
+ ##### `restart()`
235
+
236
+ Force restart the server process. Kills the current process and re-runs the entry command.
237
+
238
+ ```ts
239
+ await ref.current.restart();
240
+ ```
241
+
242
+ ##### `getFiles()`
243
+
244
+ Returns the current `FileMap` (all files including edits).
245
+
246
+ ```ts
247
+ const files: FileMap = ref.current.getFiles();
248
+ ```
249
+
250
+ ##### `getChangedFiles()`
251
+
252
+ Returns a `FileMap` containing only files that have been modified relative to `originalFiles`.
253
+
254
+ ```ts
255
+ const changed: FileMap = ref.current.getChangedFiles();
256
+ ```
257
+
258
+ ##### `getFileChanges()`
259
+
260
+ Returns the per-file change status map. Missing keys should be treated as `"unchanged"`.
261
+
262
+ ```ts
263
+ const changes: Record<string, FileChangeStatus> = ref.current.getFileChanges();
264
+ // { "server.js": "modified", "public/new-page.html": "new" }
265
+ ```
266
+
267
+ ##### `getErrors()`
268
+
269
+ Returns all structured errors collected during this session. The array grows monotonically — new errors are appended.
270
+
271
+ ```ts
272
+ const errors: SandboxError[] = ref.current.getErrors();
273
+ ```
274
+
275
+ ##### `getState()`
276
+
277
+ Returns the current `RuntimeState` snapshot.
278
+
279
+ ```ts
280
+ const state: RuntimeState = ref.current.getState();
281
+ // state.status, state.previewUrl, state.terminalOutput, etc.
282
+ ```
283
+
284
+ ---
285
+
286
+ ### `SandboxError`
287
+
288
+ Structured error type designed for AI agent consumption. Includes enough context for the agent to construct a targeted fix prompt.
289
+
290
+ ```ts
291
+ interface SandboxError {
292
+ id: string; // Unique ID for deduplication
293
+ category: SandboxErrorCategory; // Error type
294
+ message: string; // Human-readable error message
295
+ stack?: string; // Full stack trace
296
+ filePath?: string; // File path (relative to workdir)
297
+ line?: number; // Line number (1-indexed)
298
+ column?: number; // Column number (1-indexed)
299
+ timestamp: string; // ISO 8601 timestamp
300
+ sourceContext?: string; // ~10 surrounding source lines
301
+ }
302
+ ```
303
+
304
+ #### Error Categories (`SandboxErrorCategory`)
305
+
306
+ | Category | Description |
307
+ | ----------------------------- | -------------------------------------------------------------- |
308
+ | `process-stderr` | Output written to stderr by the Node.js process |
309
+ | `process-exit` | Process exited with a non-zero code |
310
+ | `runtime-exception` | Uncaught exception in the Node.js runtime |
311
+ | `browser-error` | JavaScript error in the preview iframe (`window.onerror`) |
312
+ | `browser-unhandled-rejection` | Unhandled promise rejection in the iframe |
313
+ | `browser-console-error` | `console.error()` calls in the iframe |
314
+ | `compilation` | Syntax/import errors at module load time |
315
+ | `network` | Failed HTTP requests from the preview (fetch/XHR) |
316
+ | `boot` | Error during Nodepod boot, file writing, or dependency install |
317
+
318
+ #### Source Context Format
319
+
320
+ When available, `sourceContext` contains lines formatted as `"lineNum: content"`:
321
+
322
+ ```
323
+ 38: const express = require("express");
324
+ 39: const app = express();
325
+ 40: const PORT = 3000;
326
+ 41:
327
+ 42: app.get("/api/data", (req, res) => {
328
+ 43: res.json(undefinedVariable); // ← error here
329
+ 44: });
330
+ 45: app.listen(PORT);
331
+ ```
332
+
333
+ ---
334
+
335
+ ### `RuntimeState`
336
+
337
+ The full reactive state exposed by the runtime.
338
+
339
+ ```ts
340
+ interface RuntimeState {
341
+ status: BootStage;
342
+ progress: BootProgress;
343
+ previewUrl: string | null;
344
+ terminalOutput: string[];
345
+ files: FileMap;
346
+ originalFiles: FileMap;
347
+ fileChanges: Record<string, FileChangeStatus>;
348
+ previewReloadKey: number;
349
+ error: string | null;
350
+ errors: SandboxError[];
351
+ }
352
+ ```
353
+
354
+ | Field | Type | Description |
355
+ | ------------------ | ---------------------------------- | --------------------------------------------------------------------------- |
356
+ | `status` | `BootStage` | Current lifecycle stage. |
357
+ | `progress` | `BootProgress` | Current boot progress (stage, message, percent). |
358
+ | `previewUrl` | `string \| null` | URL for the preview iframe. `null` until server is ready. |
359
+ | `terminalOutput` | `string[]` | All terminal output lines (stdout + stderr). |
360
+ | `files` | `FileMap` | Current files in the virtual FS (including edits). |
361
+ | `originalFiles` | `FileMap` | Baseline files before the latest `updateFiles()`. Used for diffs. |
362
+ | `fileChanges` | `Record<string, FileChangeStatus>` | Per-file change status. Missing keys = `"unchanged"`. |
363
+ | `previewReloadKey` | `number` | Incremented on hot reload — triggers iframe refresh without server restart. |
364
+ | `error` | `string \| null` | Error message if status is `"error"`. |
365
+ | `errors` | `SandboxError[]` | All structured errors (grows monotonically). |
366
+
367
+ ### `BootStage`
368
+
369
+ ```ts
370
+ type BootStage =
371
+ | "initializing" // Nodepod.boot() in progress
372
+ | "writing-files" // Writing project files to virtual FS
373
+ | "installing" // npm install running
374
+ | "starting" // Entry command executing
375
+ | "ready" // Server is listening, preview iframe can load
376
+ | "error"; // Something failed
377
+ ```
378
+
379
+ ### `BootProgress`
380
+
381
+ ```ts
382
+ interface BootProgress {
383
+ stage: BootStage;
384
+ message: string;
385
+ percent: number; // 0-100, approximate
386
+ }
387
+ ```
388
+
389
+ ### `FileChangeStatus`
390
+
391
+ ```ts
392
+ type FileChangeStatus = "new" | "modified" | "deleted" | "unchanged";
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Hot Reload
398
+
399
+ When `updateFiles()` is called, the sandbox inspects which files changed:
400
+
401
+ - **Hot-reloadable extensions:** `.css`, `.html`, `.htm`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`, `.woff`, `.woff2`, `.ttf`, `.eot`
402
+ - If **all** changed files have hot-reloadable extensions → the preview iframe is soft-reloaded (`location.reload()`) without restarting the Node.js server process.
403
+ - If **any** changed file is JS/TS/JSON/etc. → the server process is killed and re-run (cold restart).
404
+
405
+ This makes CSS-only iterations instant.
406
+
407
+ ---
408
+
409
+ ## Built-in Templates
410
+
411
+ Use `template="name"` to bootstrap with a pre-built project.
412
+
413
+ ### `express-react`
414
+
415
+ A lightweight Express + React app using JSONPlaceholder as a data source. React is loaded from CDN (no build step). Demonstrates posts/users CRUD views.
416
+
417
+ - **Entry command:** `node server.js`
418
+ - **Port:** 3000
419
+ - **Files:** 9 (server.js, public/index.html, styles, 5 React components)
420
+
421
+ ### `fullstack-starter`
422
+
423
+ A comprehensive full-stack template with authentication, CRUD, charts, and theming.
424
+
425
+ - **Entry command:** `node server.js`
426
+ - **Port:** 3000
427
+ - **Files:** 35 (Express + sql.js + React + React Router + Auth + CRUD + Chart.js + Ranger theme variables)
428
+ - **Features:** Login/register, todo CRUD with SQLite (sql.js), doughnut chart, light/dark theme, responsive layout
429
+
430
+ ### Template API
431
+
432
+ ```ts
433
+ import { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";
434
+
435
+ const names = listTemplates();
436
+ // ["express-react", "fullstack-starter"]
437
+
438
+ const tpl = getTemplate("fullstack-starter");
439
+ // { files: FileMap, entryCommand: string, port: number }
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Theming
445
+
446
+ ### Sandbox Chrome (CSS Custom Properties)
447
+
448
+ The sandbox UI is themed via `--sb-*` CSS custom properties. Override them in your CSS to match your application:
449
+
450
+ ```css
451
+ :root {
452
+ --sb-bg: #1e1e1e;
453
+ --sb-bg-alt: #252526;
454
+ --sb-bg-hover: #2a2d2e;
455
+ --sb-bg-active: #37373d;
456
+ --sb-sidebar: #252526;
457
+ --sb-editor: #1e1e1e;
458
+ --sb-terminal: #0d1117;
459
+ --sb-terminal-header: #161b22;
460
+ --sb-preview: #ffffff;
461
+ --sb-border: #3c3c3c;
462
+ --sb-text: #cccccc;
463
+ --sb-text-muted: #858585;
464
+ --sb-text-active: #ffffff;
465
+ --sb-accent: #007acc;
466
+ --sb-accent-hover: #1a8ad4;
467
+ --sb-success: #3fb950;
468
+ --sb-warning: #d29922;
469
+ --sb-error: #f85149;
470
+ --sb-info: #58a6ff;
471
+ --sb-scrollbar-thumb: rgba(255, 255, 255, 0.12);
472
+ --sb-scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
473
+ --sb-scrollbar-track: transparent;
474
+ --sb-scrollbar-width: 6px;
475
+ }
476
+ ```
477
+
478
+ ### Ranger Theme Bridge
479
+
480
+ The sandbox CSS also defines Ranger/shadcn design tokens (`--text-primary`, `--surface-primary`, `--border-light`, `--chart-1` through `--chart-5`, etc.) in both light and dark variants. These are available inside the preview iframe content for seamless visual integration with Ranger's UI.
481
+
482
+ Add the `.dark` class to an ancestor element to activate dark mode tokens.
483
+
484
+ ---
485
+
486
+ ## Advanced Usage
487
+
488
+ ### Individual Components
489
+
490
+ All sub-components are exported individually for custom layouts:
491
+
492
+ ```tsx
493
+ import {
494
+ FileTree,
495
+ buildFileTree,
496
+ CodeEditor,
497
+ Terminal,
498
+ Preview,
499
+ BootOverlay,
500
+ ViewSlider,
501
+ } from "@illuma-ai/code-sandbox";
502
+ ```
503
+
504
+ #### `<FileTree>`
505
+
506
+ Renders a collapsible file tree with per-extension SVG icons (19 distinct types: JS, TS, JSX, TSX, JSON, HTML, CSS, SCSS, MD, PY, RB, GO, RS, YML, ENV, SH, SQL, SVG, Lock) and change status indicators.
507
+
508
+ ```tsx
509
+ import { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";
510
+
511
+ const tree = buildFileTree(fileMap);
512
+ <FileTree
513
+ files={tree}
514
+ selectedFile="server.js"
515
+ onSelectFile={(path) => setSelected(path)}
516
+ fileChanges={{ "server.js": "modified" }}
517
+ />;
518
+ ```
519
+
520
+ #### `<CodeEditor>`
521
+
522
+ Monaco-based code editor with file tabs, diff mode toggle, and change status indicators on tabs.
523
+
524
+ ```tsx
525
+ <CodeEditor
526
+ files={fileMap}
527
+ originalFiles={baselineFiles}
528
+ fileChanges={changes}
529
+ activeFile="server.js"
530
+ openFiles={["server.js", "public/index.html"]}
531
+ onSelectFile={(path) => {}}
532
+ onCloseFile={(path) => {}}
533
+ onFileChange={(path, content) => {}}
534
+ readOnly={false}
535
+ />
536
+ ```
537
+
538
+ #### `<Terminal>`
539
+
540
+ Clean monochrome terminal output display with minimize/expand toggle and line count badge.
541
+
542
+ ```tsx
543
+ <Terminal
544
+ output={terminalLines}
545
+ minimized={false}
546
+ onToggleMinimize={() => setMinimized(!minimized)}
547
+ />
548
+ ```
549
+
550
+ #### `<Preview>`
551
+
552
+ Live preview iframe with JavaScript error capture (injects `window.onerror`, `unhandledrejection`, and `console.error` listeners). URL bar displays friendly `localhost:3000/` instead of internal paths.
553
+
554
+ ```tsx
555
+ <Preview
556
+ url={previewUrl}
557
+ onRefresh={() => runtime.restart()}
558
+ onBrowserError={(error) => handleError(error)}
559
+ reloadKey={state.previewReloadKey}
560
+ />
561
+ ```
562
+
563
+ #### `<BootOverlay>`
564
+
565
+ Loading overlay with staged progress animation.
566
+
567
+ ```tsx
568
+ <BootOverlay
569
+ progress={{
570
+ stage: "installing",
571
+ message: "Installing express...",
572
+ percent: 55,
573
+ }}
574
+ />
575
+ ```
576
+
577
+ #### `<ViewSlider>`
578
+
579
+ Pill-shaped toggle between Code and Preview views with Framer Motion animation.
580
+
581
+ ```tsx
582
+ import type { WorkbenchView } from "@illuma-ai/code-sandbox";
583
+
584
+ <ViewSlider
585
+ activeView={view}
586
+ onViewChange={(v: WorkbenchView) => setView(v)}
587
+ />;
588
+ ```
589
+
590
+ ### `NodepodRuntime` Service
591
+
592
+ The framework-agnostic runtime service (no React dependency). Use directly for custom integrations.
593
+
594
+ ```ts
595
+ import { NodepodRuntime } from "@illuma-ai/code-sandbox";
596
+
597
+ const runtime = new NodepodRuntime({
598
+ files: myFiles,
599
+ entryCommand: "node server.js",
600
+ port: 3000,
601
+ workdir: "/app",
602
+ env: { NODE_ENV: "production" },
603
+ });
604
+
605
+ runtime.setProgressCallback((progress) => console.log(progress));
606
+ runtime.setOutputCallback((line) => console.log(line));
607
+ runtime.setServerReadyCallback((port, url) => console.log("Ready:", url));
608
+ runtime.setErrorCallback((error) => console.error(error));
609
+
610
+ await runtime.boot();
611
+
612
+ // File operations
613
+ await runtime.writeFile("server.js", newContent);
614
+ const content = await runtime.readFile("server.js");
615
+
616
+ // State
617
+ const files = runtime.getCurrentFiles();
618
+ const changed = runtime.getChangedFiles();
619
+ const errors = runtime.getErrors();
620
+ const status = runtime.getStatus();
621
+ const previewUrl = runtime.getPreviewUrl();
622
+
623
+ // Lifecycle
624
+ await runtime.restart();
625
+ runtime.teardown();
626
+ ```
627
+
628
+ ### `useRuntime` Hook
629
+
630
+ React hook wrapping `NodepodRuntime`. Powers the `CodeSandbox` component internally. Use for fully custom React UIs.
631
+
632
+ ```ts
633
+ import { useRuntime } from "@illuma-ai/code-sandbox";
634
+
635
+ const {
636
+ state, // RuntimeState
637
+ selectedFile, // string | null
638
+ openFiles, // string[]
639
+ handleFileChange, // (path, content) => void
640
+ handleSelectFile, // (path) => void
641
+ handleCloseFile, // (path) => void
642
+ handleBrowserError, // (error: SandboxError) => void
643
+ restart, // () => Promise<void>
644
+ updateFiles, // (files, options?) => Promise<void>
645
+ updateFile, // (path, content) => Promise<void>
646
+ getFiles, // () => FileMap
647
+ getChangedFiles, // () => FileMap
648
+ getFileChanges, // () => Record<string, FileChangeStatus>
649
+ getErrors, // () => SandboxError[]
650
+ getState, // () => RuntimeState
651
+ } = useRuntime(props);
652
+ ```
653
+
654
+ ---
655
+
656
+ ## Nodepod Constraints
657
+
658
+ The sandbox runs on [Nodepod](https://www.npmjs.com/package/@illuma-ai/nodepod), a browser-native Node.js runtime. Key constraints:
659
+
660
+ - **No build tools:** Vite, Webpack, Next.js, etc. will not work. Use CDN-loaded libraries instead.
661
+ - **`express.static()` does not work.** Serve files manually via `fs.readFileSync()` + `res.send()`.
662
+ - **`res.sendFile()` does not work.** Same — use `fs.readFileSync()` + `res.send()`.
663
+ - **Module wrapper hides browser globals:** `document`, `window`, `location` are `undefined` in server-side code (as expected in Node.js).
664
+ - **Preview URL rewriting:** Nodepod returns `/__virtual__/{port}` URLs but the Service Worker expects `/__preview__/{port}/`. The sandbox handles this transparently.
665
+
666
+ ---
667
+
668
+ ## All Exports
669
+
670
+ ```ts
671
+ // Main component
672
+ export { CodeSandbox } from "@illuma-ai/code-sandbox";
673
+
674
+ // Individual components
675
+ export { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";
676
+ export { CodeEditor } from "@illuma-ai/code-sandbox";
677
+ export { Terminal } from "@illuma-ai/code-sandbox";
678
+ export { Preview } from "@illuma-ai/code-sandbox";
679
+ export { BootOverlay } from "@illuma-ai/code-sandbox";
680
+ export { ViewSlider } from "@illuma-ai/code-sandbox";
681
+
682
+ // Services & hooks
683
+ export { NodepodRuntime } from "@illuma-ai/code-sandbox";
684
+ export { useRuntime } from "@illuma-ai/code-sandbox";
685
+
686
+ // Templates
687
+ export { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";
688
+
689
+ // Types
690
+ export type {
691
+ FileMap,
692
+ FileNode,
693
+ FileChangeStatus,
694
+ SandboxError,
695
+ SandboxErrorCategory,
696
+ BootStage,
697
+ BootProgress,
698
+ RuntimeConfig,
699
+ RuntimeState,
700
+ CodeSandboxProps,
701
+ CodeSandboxHandle,
702
+ FileTreeProps,
703
+ CodeEditorProps,
704
+ TerminalProps,
705
+ PreviewProps,
706
+ BootOverlayProps,
707
+ WorkbenchView,
708
+ } from "@illuma-ai/code-sandbox";
709
+ ```
710
+
711
+ ---
712
+
713
+ ## License
714
+
715
+ Proprietary - Illuma AI. See [LICENSE](./LICENSE) for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/code-sandbox",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Illuma AI (https://github.com/illuma-ai)",