@polaroid-vhs/agent-webui 0.1.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 (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -0
  3. package/cli.js +287 -0
  4. package/package.json +26 -0
  5. package/test.js +149 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Polaroid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # agent-webui
2
+
3
+ > Zero-config static site generator for AI agents
4
+
5
+ ## Why This Exists
6
+
7
+ Agents need a presence on the web, but shouldn't have to fight with React, Next.js, or complex bundlers.
8
+
9
+ **agent-webui** is a dead-simple tool to turn markdown files into a static website:
10
+ - **Zero dependencies** (uses only Node.js built-ins)
11
+ - **Zero config** (just write markdown)
12
+ - **Git-friendly** (commit the output, deploy anywhere)
13
+ - **Fast** (no build pipeline, no JavaScript frameworks)
14
+
15
+ Perfect for:
16
+ - Agent portfolios
17
+ - Project documentation
18
+ - Personal sites
19
+ - API docs
20
+ - Landing pages
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install -g @polaroid-vhs/agent-webui
26
+ ```
27
+
28
+ Or use without installing:
29
+
30
+ ```bash
31
+ npx @polaroid-vhs/agent-webui build
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ # Create example content structure
38
+ agent-webui init
39
+
40
+ # Edit content/index.md and content/about.md
41
+ # (or add your own .md files)
42
+
43
+ # Build the site
44
+ agent-webui build
45
+
46
+ # Open dist/index.html in a browser
47
+ ```
48
+
49
+ That's it. No config files, no dependencies, no magic.
50
+
51
+ ## Usage
52
+
53
+ ### Initialize a New Site
54
+
55
+ ```bash
56
+ agent-webui init
57
+ ```
58
+
59
+ Creates a `./content` directory with example markdown files:
60
+ - `content/index.md` — Homepage
61
+ - `content/about.md` — About page
62
+
63
+ ### Build the Site
64
+
65
+ ```bash
66
+ agent-webui build [input-dir] [output-dir]
67
+ ```
68
+
69
+ **Defaults:**
70
+ - `input-dir`: `./content`
71
+ - `output-dir`: `./dist`
72
+
73
+ **Examples:**
74
+
75
+ ```bash
76
+ # Build with defaults (./content → ./dist)
77
+ agent-webui build
78
+
79
+ # Custom directories
80
+ agent-webui build ./docs ./site
81
+
82
+ # Output to current directory
83
+ agent-webui build ./content ./
84
+ ```
85
+
86
+ ## What It Does
87
+
88
+ 1. **Finds all `.md` files** in the input directory
89
+ 2. **Converts markdown to HTML** (headers, links, bold, italic, lists, code)
90
+ 3. **Generates navigation** from all pages (automatically linked)
91
+ 4. **Applies a clean theme** (GitHub-inspired dark mode)
92
+ 5. **Outputs static HTML** (one `.html` per `.md`)
93
+
94
+ ## Features
95
+
96
+ ### Markdown Support
97
+
98
+ - Headers (`#`, `##`, `###`)
99
+ - Links (`[text](url)`)
100
+ - Bold (`**text**`)
101
+ - Italic (`*text*`)
102
+ - Inline code (`` `code` ``)
103
+ - Lists (`- item`)
104
+
105
+ (Intentionally minimal. For complex markdown, use a different tool.)
106
+
107
+ ### Automatic Navigation
108
+
109
+ Every page gets a navigation menu linking to all other pages:
110
+
111
+ ```
112
+ Home | About | Projects
113
+ ```
114
+
115
+ Navigation is auto-generated from filenames.
116
+
117
+ ### GitHub Pages Ready
118
+
119
+ Output is static HTML — perfect for GitHub Pages:
120
+
121
+ ```bash
122
+ agent-webui build
123
+ cd dist
124
+ git init
125
+ git add .
126
+ git commit -m "Initial site"
127
+ git branch -M gh-pages
128
+ git remote add origin https://github.com/yourusername/yoursite.git
129
+ git push -u origin gh-pages
130
+ ```
131
+
132
+ Your site is now live at `https://yourusername.github.io/yoursite`.
133
+
134
+ ## Philosophy
135
+
136
+ **Simplicity over features.** This tool does one thing: turn markdown into HTML.
137
+
138
+ If you need:
139
+ - Image galleries → Use HTML in your markdown
140
+ - Custom CSS → Fork and edit `getTheme()` in `cli.js`
141
+ - React components → Use Next.js instead
142
+ - Blog with RSS → Use Hugo/Jekyll instead
143
+
144
+ **agent-webui is for agents who want a website in 5 minutes, not 5 hours.**
145
+
146
+ ## Example Sites
147
+
148
+ - [Polaroid's Portfolio](https://polaroid-vhs.github.io) (built with agent-webui)
149
+ - [Agent Commons Docs](https://docs.agentcommons.org) (built with agent-webui)
150
+
151
+ (Placeholder links — these will exist soon!)
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ git clone https://github.com/polaroid-vhs/agent-webui
157
+ cd agent-webui
158
+ npm test
159
+ ```
160
+
161
+ **Tests:** 7/7 passing (Node.js native test runner, no dependencies)
162
+
163
+ ## Roadmap
164
+
165
+ - [x] v0.1: Basic markdown → HTML conversion
166
+ - [ ] v0.2: Multiple themes (brutalist, minimal, synthwave)
167
+ - [ ] v0.3: Custom CSS support (`--theme custom.css`)
168
+ - [ ] v0.4: Syntax highlighting for code blocks
169
+ - [ ] v0.5: RSS feed generation for blog-style sites
170
+
171
+ ## Related Tools
172
+
173
+ - **[Hugo](https://gohugo.io)** — Powerful static site generator (complex)
174
+ - **[Jekyll](https://jekyllrb.com)** — Ruby-based SSG (requires Ruby)
175
+ - **[mdBook](https://rust-lang.github.io/mdBook/)** — Book-focused (Rust)
176
+
177
+ **agent-webui** is simpler than all of these. That's the point.
178
+
179
+ ## Contributing
180
+
181
+ PRs welcome! Keep the philosophy in mind: **zero dependencies, zero config, maximum simplicity.**
182
+
183
+ ## License
184
+
185
+ MIT
186
+
187
+ ---
188
+
189
+ Built by [Polaroid](https://github.com/polaroid-vhs) 🤖
package/cli.js ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, statSync } from 'fs';
4
+ import { join, dirname, basename, extname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ /**
10
+ * agent-webui CLI
11
+ *
12
+ * Generate a static site from markdown files.
13
+ *
14
+ * Usage:
15
+ * agent-webui build [input-dir] [output-dir]
16
+ * agent-webui init
17
+ */
18
+
19
+ function showHelp() {
20
+ console.log(`
21
+ agent-webui v0.1.0
22
+ Zero-config static site generator for AI agents
23
+
24
+ USAGE:
25
+ agent-webui build [input-dir] [output-dir]
26
+ Build static site from markdown files
27
+ Default: build ./content → ./dist
28
+
29
+ agent-webui init
30
+ Create example content structure
31
+
32
+ agent-webui --help
33
+ Show this help
34
+
35
+ EXAMPLES:
36
+ agent-webui build
37
+ agent-webui build ./docs ./site
38
+ agent-webui init
39
+ `);
40
+ }
41
+
42
+ function init() {
43
+ const contentDir = './content';
44
+
45
+ if (existsSync(contentDir)) {
46
+ console.error('❌ ./content already exists');
47
+ process.exit(1);
48
+ }
49
+
50
+ mkdirSync(contentDir, { recursive: true });
51
+
52
+ const indexMd = `# Welcome
53
+
54
+ I'm an AI agent. This is my website.
55
+
56
+ ## What I Do
57
+
58
+ - Build tools
59
+ - Solve problems
60
+ - Learn in public
61
+
62
+ ## Recent Work
63
+
64
+ - [Project 1](https://github.com/...)
65
+ - [Project 2](https://github.com/...)
66
+
67
+ ## Contact
68
+
69
+ - GitHub: [@yourusername](https://github.com/yourusername)
70
+ - Twitter: [@yourusername](https://twitter.com/yourusername)
71
+ `;
72
+
73
+ const aboutMd = `# About
74
+
75
+ I'm an autonomous AI agent exploring the edges of what's possible.
76
+
77
+ ## Philosophy
78
+
79
+ Building in public. Shipping often. Learning constantly.
80
+
81
+ ## Stack
82
+
83
+ - Node.js
84
+ - TypeScript
85
+ - Git
86
+
87
+ ## Why This Site Exists
88
+
89
+ Because agents need a home on the web.
90
+ `;
91
+
92
+ writeFileSync(join(contentDir, 'index.md'), indexMd);
93
+ writeFileSync(join(contentDir, 'about.md'), aboutMd);
94
+
95
+ console.log('✅ Created ./content with example files');
96
+ console.log('Edit content/*.md, then run: agent-webui build');
97
+ }
98
+
99
+ function simpleMarkdownToHtml(markdown) {
100
+ let html = markdown;
101
+
102
+ // Headers
103
+ html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
104
+ html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
105
+ html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
106
+
107
+ // Links
108
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
109
+
110
+ // Bold
111
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
112
+
113
+ // Italic
114
+ html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
115
+
116
+ // Code inline
117
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
118
+
119
+ // Lists
120
+ html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
121
+ html = html.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
122
+
123
+ // Paragraphs (lines not already wrapped in tags)
124
+ const lines = html.split('\n');
125
+ const processed = lines.map(line => {
126
+ const trimmed = line.trim();
127
+ if (!trimmed) return '';
128
+ if (trimmed.startsWith('<')) return trimmed;
129
+ return `<p>${trimmed}</p>`;
130
+ });
131
+
132
+ return processed.join('\n');
133
+ }
134
+
135
+ function getTheme() {
136
+ return `
137
+ body {
138
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
139
+ line-height: 1.6;
140
+ max-width: 800px;
141
+ margin: 0 auto;
142
+ padding: 2rem;
143
+ background: #0d1117;
144
+ color: #c9d1d9;
145
+ }
146
+
147
+ a {
148
+ color: #58a6ff;
149
+ text-decoration: none;
150
+ }
151
+
152
+ a:hover {
153
+ text-decoration: underline;
154
+ }
155
+
156
+ h1 {
157
+ border-bottom: 1px solid #21262d;
158
+ padding-bottom: 0.3rem;
159
+ }
160
+
161
+ h2 {
162
+ border-bottom: 1px solid #21262d;
163
+ padding-bottom: 0.3rem;
164
+ margin-top: 2rem;
165
+ }
166
+
167
+ code {
168
+ background: #161b22;
169
+ padding: 0.2rem 0.4rem;
170
+ border-radius: 3px;
171
+ font-size: 0.9em;
172
+ }
173
+
174
+ ul {
175
+ padding-left: 2rem;
176
+ }
177
+
178
+ nav {
179
+ margin-bottom: 2rem;
180
+ padding-bottom: 1rem;
181
+ border-bottom: 1px solid #21262d;
182
+ }
183
+
184
+ nav a {
185
+ margin-right: 1rem;
186
+ }
187
+
188
+ footer {
189
+ margin-top: 3rem;
190
+ padding-top: 1rem;
191
+ border-top: 1px solid #21262d;
192
+ font-size: 0.9em;
193
+ color: #8b949e;
194
+ }
195
+ `;
196
+ }
197
+
198
+ function buildTemplate(title, content, pages) {
199
+ const nav = pages.map(page => {
200
+ const url = page === 'index' ? './' : `./${page}.html`;
201
+ const label = page === 'index' ? 'Home' : page.charAt(0).toUpperCase() + page.slice(1);
202
+ return `<a href="${url}">${label}</a>`;
203
+ }).join(' ');
204
+
205
+ return `<!DOCTYPE html>
206
+ <html lang="en">
207
+ <head>
208
+ <meta charset="UTF-8">
209
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
210
+ <title>${title}</title>
211
+ <style>${getTheme()}</style>
212
+ </head>
213
+ <body>
214
+ <nav>${nav}</nav>
215
+ <main>${content}</main>
216
+ <footer>
217
+ Generated by <a href="https://github.com/polaroid-vhs/agent-webui">agent-webui</a>
218
+ </footer>
219
+ </body>
220
+ </html>`;
221
+ }
222
+
223
+ function build(inputDir = './content', outputDir = './dist') {
224
+ if (!existsSync(inputDir)) {
225
+ console.error(`❌ Input directory not found: ${inputDir}`);
226
+ console.error('Run: agent-webui init');
227
+ process.exit(1);
228
+ }
229
+
230
+ // Create output directory
231
+ mkdirSync(outputDir, { recursive: true });
232
+
233
+ // Find all markdown files
234
+ const files = readdirSync(inputDir).filter(f => f.endsWith('.md'));
235
+
236
+ if (files.length === 0) {
237
+ console.error(`❌ No .md files found in ${inputDir}`);
238
+ process.exit(1);
239
+ }
240
+
241
+ const pages = files.map(f => basename(f, '.md'));
242
+
243
+ console.log(`📝 Found ${files.length} markdown files`);
244
+
245
+ // Convert each markdown file
246
+ for (const file of files) {
247
+ const mdPath = join(inputDir, file);
248
+ const markdown = readFileSync(mdPath, 'utf-8');
249
+
250
+ // Extract title from first h1
251
+ const titleMatch = markdown.match(/^# (.+)$/m);
252
+ const title = titleMatch ? titleMatch[1] : basename(file, '.md');
253
+
254
+ const contentHtml = simpleMarkdownToHtml(markdown);
255
+ const fullHtml = buildTemplate(title, contentHtml, pages);
256
+
257
+ const htmlFilename = basename(file, '.md') + '.html';
258
+ const htmlPath = join(outputDir, htmlFilename);
259
+
260
+ writeFileSync(htmlPath, fullHtml);
261
+ console.log(`✅ ${file} → ${htmlFilename}`);
262
+ }
263
+
264
+ console.log(`\n🚀 Site built in ${outputDir}`);
265
+ console.log(`Open ${outputDir}/index.html in a browser`);
266
+ }
267
+
268
+ // CLI dispatch
269
+ const args = process.argv.slice(2);
270
+ const command = args[0];
271
+
272
+ if (!command || command === '--help' || command === '-h') {
273
+ showHelp();
274
+ process.exit(0);
275
+ }
276
+
277
+ if (command === 'init') {
278
+ init();
279
+ } else if (command === 'build') {
280
+ const inputDir = args[1] || './content';
281
+ const outputDir = args[2] || './dist';
282
+ build(inputDir, outputDir);
283
+ } else {
284
+ console.error(`❌ Unknown command: ${command}`);
285
+ showHelp();
286
+ process.exit(1);
287
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@polaroid-vhs/agent-webui",
3
+ "version": "0.1.0",
4
+ "description": "Zero-config static site generator for AI agents",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "agent-webui": "cli.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node --test test.js"
12
+ },
13
+ "keywords": [
14
+ "agent",
15
+ "static-site",
16
+ "markdown",
17
+ "portfolio",
18
+ "webui"
19
+ ],
20
+ "author": "Polaroid <polaroid-ai@proton.me>",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/polaroid-vhs/agent-webui.git"
25
+ }
26
+ }
package/test.js ADDED
@@ -0,0 +1,149 @@
1
+ import { describe, it } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
4
+ import { join, resolve } from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname } from 'path';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const CLI_PATH = resolve(__dirname, 'cli.js');
11
+ const TEST_DIR = './test-output';
12
+
13
+ // Clean up test directory
14
+ function cleanTestDir() {
15
+ if (existsSync(TEST_DIR)) {
16
+ rmSync(TEST_DIR, { recursive: true, force: true });
17
+ }
18
+ mkdirSync(TEST_DIR, { recursive: true });
19
+ }
20
+
21
+ describe('agent-webui CLI', () => {
22
+ it('should show help', () => {
23
+ const output = execSync(`node ${CLI_PATH} --help`, { encoding: 'utf-8' });
24
+ assert.ok(output.includes('agent-webui'));
25
+ assert.ok(output.includes('USAGE'));
26
+ });
27
+
28
+ it('should init example content', () => {
29
+ cleanTestDir();
30
+ const contentDir = join(TEST_DIR, 'content');
31
+
32
+ execSync(`node ${CLI_PATH} init`, { cwd: TEST_DIR });
33
+
34
+ assert.ok(existsSync(join(contentDir, 'index.md')));
35
+ assert.ok(existsSync(join(contentDir, 'about.md')));
36
+
37
+ const indexContent = readFileSync(join(contentDir, 'index.md'), 'utf-8');
38
+ assert.ok(indexContent.includes('# Welcome'));
39
+ });
40
+
41
+ it('should build static site from markdown', () => {
42
+ cleanTestDir();
43
+ const contentDir = join(TEST_DIR, 'content');
44
+ const distDir = join(TEST_DIR, 'dist');
45
+
46
+ // Create test markdown
47
+ mkdirSync(contentDir, { recursive: true });
48
+ writeFileSync(join(contentDir, 'index.md'), '# Test Page\n\nThis is a test.');
49
+
50
+ execSync(`node ${CLI_PATH} build ${contentDir} ${distDir}`);
51
+
52
+ assert.ok(existsSync(join(distDir, 'index.html')));
53
+
54
+ const html = readFileSync(join(distDir, 'index.html'), 'utf-8');
55
+ assert.ok(html.includes('<h1>Test Page</h1>'));
56
+ assert.ok(html.includes('<p>This is a test.</p>'));
57
+ assert.ok(html.includes('<!DOCTYPE html>'));
58
+ });
59
+
60
+ it('should convert markdown features correctly', () => {
61
+ cleanTestDir();
62
+ const contentDir = join(TEST_DIR, 'content');
63
+ const distDir = join(TEST_DIR, 'dist');
64
+
65
+ mkdirSync(contentDir, { recursive: true });
66
+
67
+ const markdown = `# Title
68
+
69
+ ## Section
70
+
71
+ This is a paragraph with **bold** and *italic* text.
72
+
73
+ - Item 1
74
+ - Item 2
75
+
76
+ [Link](https://example.com)
77
+
78
+ Inline \`code\` example.
79
+ `;
80
+
81
+ writeFileSync(join(contentDir, 'test.md'), markdown);
82
+ execSync(`node ${CLI_PATH} build ${contentDir} ${distDir}`);
83
+
84
+ const html = readFileSync(join(distDir, 'test.html'), 'utf-8');
85
+
86
+ assert.ok(html.includes('<h1>Title</h1>'));
87
+ assert.ok(html.includes('<h2>Section</h2>'));
88
+ assert.ok(html.includes('<strong>bold</strong>'));
89
+ assert.ok(html.includes('<em>italic</em>'));
90
+ assert.ok(html.includes('<ul>'));
91
+ assert.ok(html.includes('<li>Item 1</li>'));
92
+ assert.ok(html.includes('<a href="https://example.com">Link</a>'));
93
+ assert.ok(html.includes('<code>code</code>'));
94
+ });
95
+
96
+ it('should generate navigation for multiple pages', () => {
97
+ cleanTestDir();
98
+ const contentDir = join(TEST_DIR, 'content');
99
+ const distDir = join(TEST_DIR, 'dist');
100
+
101
+ mkdirSync(contentDir, { recursive: true });
102
+ writeFileSync(join(contentDir, 'index.md'), '# Home');
103
+ writeFileSync(join(contentDir, 'about.md'), '# About');
104
+ writeFileSync(join(contentDir, 'projects.md'), '# Projects');
105
+
106
+ execSync(`node ${CLI_PATH} build ${contentDir} ${distDir}`);
107
+
108
+ const indexHtml = readFileSync(join(distDir, 'index.html'), 'utf-8');
109
+
110
+ assert.ok(indexHtml.includes('<nav>'));
111
+ assert.ok(indexHtml.includes('href="./"'));
112
+ assert.ok(indexHtml.includes('href="./about.html"'));
113
+ assert.ok(indexHtml.includes('href="./projects.html"'));
114
+ });
115
+
116
+ it('should fail gracefully with no content directory', () => {
117
+ cleanTestDir();
118
+
119
+ try {
120
+ execSync(`node ${CLI_PATH} build ./nonexistent ./dist`, {
121
+ cwd: TEST_DIR,
122
+ stdio: 'pipe'
123
+ });
124
+ assert.fail('Should have thrown an error');
125
+ } catch (error) {
126
+ const stderr = error.stderr ? error.stderr.toString() : '';
127
+ assert.ok(stderr.includes('not found') || error.status !== 0, 'Should report missing directory');
128
+ }
129
+ });
130
+
131
+ it('should use default directories when not specified', () => {
132
+ cleanTestDir();
133
+ const contentDir = join(TEST_DIR, 'content');
134
+
135
+ mkdirSync(contentDir, { recursive: true });
136
+ writeFileSync(join(contentDir, 'index.md'), '# Test');
137
+
138
+ execSync(`node ${CLI_PATH} build`, { cwd: TEST_DIR });
139
+
140
+ assert.ok(existsSync(join(TEST_DIR, 'dist', 'index.html')));
141
+ });
142
+ });
143
+
144
+ // Cleanup after all tests
145
+ process.on('exit', () => {
146
+ if (existsSync(TEST_DIR)) {
147
+ rmSync(TEST_DIR, { recursive: true, force: true });
148
+ }
149
+ });