@aiworkbench/vibe-ide 0.0.3 → 0.0.4

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 (55) hide show
  1. package/dist/core/boot.d.ts +3 -0
  2. package/dist/core/boot.d.ts.map +1 -1
  3. package/dist/core/bridge-shim.d.ts +1 -1
  4. package/dist/core/bridge-shim.d.ts.map +1 -1
  5. package/dist/core/filesystem.d.ts +72 -1
  6. package/dist/core/filesystem.d.ts.map +1 -1
  7. package/dist/core/local-snapshot-cache.d.ts +72 -0
  8. package/dist/core/local-snapshot-cache.d.ts.map +1 -0
  9. package/dist/core/path-policy.d.ts +77 -0
  10. package/dist/core/path-policy.d.ts.map +1 -0
  11. package/dist/core/preview-channel.d.ts +2 -2
  12. package/dist/core/process-registry.d.ts +71 -0
  13. package/dist/core/process-registry.d.ts.map +1 -0
  14. package/dist/core/process.d.ts +5 -0
  15. package/dist/core/process.d.ts.map +1 -1
  16. package/dist/core/write-skills.d.ts +18 -0
  17. package/dist/core/write-skills.d.ts.map +1 -0
  18. package/dist/ide-theme.css +46 -1
  19. package/dist/index.d.ts +8 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +11368 -5115
  22. package/dist/mcp/mcp-adapter.d.ts +5 -6
  23. package/dist/mcp/mcp-adapter.d.ts.map +1 -1
  24. package/dist/power-user.d.ts +13 -0
  25. package/dist/power-user.d.ts.map +1 -0
  26. package/dist/power-user.js +4554 -0
  27. package/dist/prompts/core-rules.d.ts +20 -0
  28. package/dist/prompts/core-rules.d.ts.map +1 -0
  29. package/dist/prompts/skills.d.ts +36 -0
  30. package/dist/prompts/skills.d.ts.map +1 -0
  31. package/dist/prompts/skills.generated.d.ts +17 -0
  32. package/dist/prompts/skills.generated.d.ts.map +1 -0
  33. package/dist/react/code-editor.d.ts +18 -0
  34. package/dist/react/code-editor.d.ts.map +1 -0
  35. package/dist/react/file-explorer.d.ts +17 -0
  36. package/dist/react/file-explorer.d.ts.map +1 -0
  37. package/dist/react/provider.d.ts.map +1 -1
  38. package/dist/react/terminal-view.d.ts +19 -0
  39. package/dist/react/terminal-view.d.ts.map +1 -0
  40. package/dist/react/use-autosave.d.ts +46 -0
  41. package/dist/react/use-autosave.d.ts.map +1 -0
  42. package/dist/react/vibe-ide-bridge-relay.d.ts.map +1 -1
  43. package/dist/react/vibe-ide-layout.d.ts +4 -0
  44. package/dist/react/vibe-ide-layout.d.ts.map +1 -1
  45. package/dist/react/vibe-ide-preview-pane.d.ts.map +1 -1
  46. package/dist/react/vibe-ide-toolbar.d.ts +2 -1
  47. package/dist/react/vibe-ide-toolbar.d.ts.map +1 -1
  48. package/dist/server.js +7 -0
  49. package/dist/templates/generated.d.ts +16 -12
  50. package/dist/templates/generated.d.ts.map +1 -1
  51. package/dist/templates/react-template.generated.d.ts +12 -0
  52. package/dist/templates/react-template.generated.d.ts.map +1 -0
  53. package/dist/types.d.ts +80 -5
  54. package/dist/types.d.ts.map +1 -1
  55. package/package.json +15 -2
@@ -0,0 +1,4554 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
13
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
14
+ }) : x)(function(x) {
15
+ if (typeof require !== "undefined")
16
+ return require.apply(this, arguments);
17
+ throw Error('Dynamic require of "' + x + '" is not supported');
18
+ });
19
+
20
+ // src/core/path-policy.ts
21
+ function normalizePath(path) {
22
+ const normalized = path.replace(/\\/g, "/").trim();
23
+ if (!normalized)
24
+ return "/";
25
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
26
+ }
27
+ function getBasename(path) {
28
+ const segments = path.split("/").filter(Boolean);
29
+ return segments[segments.length - 1] ?? "";
30
+ }
31
+ function validateAndNormalizePath(path) {
32
+ const trimmed = path.trim();
33
+ if (!trimmed) {
34
+ throw new Error("Path validation failed: path cannot be empty.");
35
+ }
36
+ if (trimmed.includes("\x00")) {
37
+ throw new Error("Path validation failed: path contains invalid characters.");
38
+ }
39
+ const normalized = trimmed.replace(/\\/g, "/");
40
+ if (normalized.startsWith("/")) {
41
+ throw new Error("Path validation failed: absolute paths are not allowed.");
42
+ }
43
+ const parts = normalized.split("/");
44
+ const resolved = [];
45
+ for (const part of parts) {
46
+ if (!part || part === ".")
47
+ continue;
48
+ if (part === "..") {
49
+ throw new Error("Path validation failed: parent-directory traversal is not allowed.");
50
+ }
51
+ resolved.push(part);
52
+ }
53
+ return `/${resolved.join("/")}`;
54
+ }
55
+ function isSecretPath(path) {
56
+ const normalized = normalizePath(path);
57
+ const basename = getBasename(normalized);
58
+ if (SECRET_EXTENSION_PATTERN.test(basename))
59
+ return true;
60
+ if (SECRET_BASENAME_PATTERNS.some((p) => p.test(basename)))
61
+ return true;
62
+ return SECRET_PATH_PATTERNS.some((p) => p.test(normalized));
63
+ }
64
+ function sanitizePath(path) {
65
+ const normalized = validateAndNormalizePath(path);
66
+ if (isSecretPath(normalized)) {
67
+ throw new Error("Path validation failed: access to secrets is denied.");
68
+ }
69
+ return normalized;
70
+ }
71
+ function isExcludedDir(name) {
72
+ return EXCLUDED_DIRS.has(name);
73
+ }
74
+ function pathContainsExcludedDir(path) {
75
+ const segments = path.split("/").filter(Boolean);
76
+ return segments.some((s) => EXCLUDED_DIRS.has(s));
77
+ }
78
+ function pathRequiresApproval(path) {
79
+ if (path === "/")
80
+ return true;
81
+ const segments = path.split("/").filter(Boolean);
82
+ if (segments.length === 0)
83
+ return true;
84
+ const basename = segments[segments.length - 1] ?? "";
85
+ if (path === "/manifest.json")
86
+ return true;
87
+ if (APPROVAL_REQUIRED_BASENAMES.has(basename))
88
+ return true;
89
+ if (APPROVAL_REQUIRED_FILE_PATTERNS.some((p) => p.test(basename)))
90
+ return true;
91
+ if (segments.some((s) => APPROVAL_REQUIRED_SEGMENTS.has(s)))
92
+ return true;
93
+ if (segments.some((s) => APPROVAL_REQUIRED_RUNTIME_DIRS.has(s)))
94
+ return true;
95
+ return false;
96
+ }
97
+ function getBlockedNpmFlag(args) {
98
+ for (const arg of args) {
99
+ if (BLOCKED_NPM_FLAGS.includes(arg))
100
+ return arg;
101
+ if (arg.startsWith("--prefix=") || arg.startsWith("--workspace=") || arg.startsWith("--workspaces=") || arg.startsWith("--include-workspace-root=") || arg.startsWith("--userconfig=") || arg.startsWith("--script-shell=") || arg.startsWith("-C=")) {
102
+ return arg;
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ function hasShellCompositionChars(args) {
108
+ return args.some((arg) => /[&|;><`]/.test(arg));
109
+ }
110
+ var EXCLUDED_DIRS, SECRET_BASENAME_PATTERNS, SECRET_EXTENSION_PATTERN, SECRET_PATH_PATTERNS, MAX_SNAPSHOT_FILES = 2000, MAX_SNAPSHOT_BYTES, MAX_FILE_BYTES, LARGE_FILE_THRESHOLD, APPROVAL_REQUIRED_BASENAMES, APPROVAL_REQUIRED_FILE_PATTERNS, APPROVAL_REQUIRED_SEGMENTS, APPROVAL_REQUIRED_RUNTIME_DIRS, BLOCKED_NPM_FLAGS;
111
+ var init_path_policy = __esm(() => {
112
+ EXCLUDED_DIRS = new Set([
113
+ "node_modules",
114
+ ".vite",
115
+ "dist",
116
+ ".cache",
117
+ ".ide"
118
+ ]);
119
+ SECRET_BASENAME_PATTERNS = [
120
+ /^\.env(?:\..*)?$/i,
121
+ /^\.npmrc$/i,
122
+ /^credentials(?:\.(?:json|ya?ml|toml|ini|env|txt))?$/i,
123
+ /^secrets?(?:\.(?:json|ya?ml|toml|ini|env|txt))?$/i,
124
+ /^tokens?(?:\.(?:json|ya?ml|toml|ini|env|txt))?$/i,
125
+ /^id_(?:rsa|dsa|ecdsa|ed25519)$/i,
126
+ /^\.?git-credentials$/i
127
+ ];
128
+ SECRET_EXTENSION_PATTERN = /\.(?:pem|key|p12|pfx|crt|cer|der)$/i;
129
+ SECRET_PATH_PATTERNS = [/\/.aws\/credentials$/i];
130
+ MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024;
131
+ MAX_FILE_BYTES = 5 * 1024 * 1024;
132
+ LARGE_FILE_THRESHOLD = 500 * 1024;
133
+ APPROVAL_REQUIRED_BASENAMES = new Set([
134
+ "package.json",
135
+ "package-lock.json",
136
+ "npm-shrinkwrap.json",
137
+ "yarn.lock",
138
+ "pnpm-lock.yaml",
139
+ "bun.lock",
140
+ "bun.lockb",
141
+ ".npmrc"
142
+ ]);
143
+ APPROVAL_REQUIRED_FILE_PATTERNS = [
144
+ /^vite\.config\.[cm]?[jt]s$/i,
145
+ /^webpack\.config\.[cm]?[jt]s$/i,
146
+ /^rollup\.config\.[cm]?[jt]s$/i,
147
+ /^esbuild\.config\.[cm]?[jt]s$/i,
148
+ /^postcss\.config\.[cm]?[jt]s$/i,
149
+ /^tailwind\.config\.[cm]?[jt]s$/i,
150
+ /^eslint\.config\.[cm]?[jt]s$/i,
151
+ /^prettier\.config\.[cm]?[jt]s$/i,
152
+ /^babel\.config\.[cm]?[jt]s$/i,
153
+ /^jest\.config\.[cm]?[jt]s$/i,
154
+ /^vitest\.config\.[cm]?[jt]s$/i,
155
+ /^tsup\.config\.[cm]?[jt]s$/i,
156
+ /^dockerfile(?:\..+)?$/i,
157
+ /\.(?:sh|bash|zsh|fish|ps1|cmd|bat)$/i
158
+ ];
159
+ APPROVAL_REQUIRED_SEGMENTS = new Set(["node_modules", ".ide"]);
160
+ APPROVAL_REQUIRED_RUNTIME_DIRS = new Set([
161
+ "dist",
162
+ "build",
163
+ ".cache",
164
+ ".vite",
165
+ ".next",
166
+ ".nuxt",
167
+ "coverage"
168
+ ]);
169
+ BLOCKED_NPM_FLAGS = [
170
+ "--prefix",
171
+ "--workspace",
172
+ "--workspaces",
173
+ "--include-workspace-root",
174
+ "-w",
175
+ "-C",
176
+ "--script-shell",
177
+ "--userconfig",
178
+ "--"
179
+ ];
180
+ });
181
+
182
+ // src/core/filesystem.ts
183
+ async function readTreeStructure(instance, basePath = "/", options) {
184
+ const excludes = new Set([...EXCLUDED_DIRS, ...options?.exclude ?? []]);
185
+ const result = [];
186
+ const entries = await instance.fs.readdir(basePath, { withFileTypes: true });
187
+ const sorted = [...entries].sort((a, b) => {
188
+ if (a.isDirectory() !== b.isDirectory()) {
189
+ return a.isDirectory() ? -1 : 1;
190
+ }
191
+ return a.name.localeCompare(b.name);
192
+ });
193
+ for (const entry of sorted) {
194
+ if (excludes.has(entry.name))
195
+ continue;
196
+ const fullPath = basePath === "/" ? `/${entry.name}` : `${basePath}/${entry.name}`;
197
+ if (isSecretPath(fullPath))
198
+ continue;
199
+ if (entry.isDirectory()) {
200
+ const children = await readTreeStructure(instance, fullPath, options);
201
+ result.push({
202
+ name: entry.name,
203
+ path: fullPath,
204
+ type: "directory",
205
+ children
206
+ });
207
+ } else {
208
+ result.push({
209
+ name: entry.name,
210
+ path: fullPath,
211
+ type: "file"
212
+ });
213
+ }
214
+ }
215
+ return result;
216
+ }
217
+ async function readFileTree(instance, basePath = "/", options = {}) {
218
+ const tree = {};
219
+ const excludes = new Set([...EXCLUDED_DIRS, ...options.exclude || []]);
220
+ async function walk(currentPath, currentTree) {
221
+ const entries = await instance.fs.readdir(currentPath, {
222
+ withFileTypes: true
223
+ });
224
+ const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
225
+ for (const entry of sorted) {
226
+ if (excludes.has(entry.name))
227
+ continue;
228
+ const fullPath = currentPath === "/" ? `/${entry.name}` : `${currentPath}/${entry.name}`;
229
+ if (isSecretPath(fullPath))
230
+ continue;
231
+ if (entry.isDirectory()) {
232
+ const dirTree = {};
233
+ currentTree[entry.name] = { directory: dirTree };
234
+ await walk(fullPath, dirTree);
235
+ } else {
236
+ const content = await instance.fs.readFile(fullPath, "utf8");
237
+ currentTree[entry.name] = { file: { contents: content } };
238
+ }
239
+ }
240
+ }
241
+ await walk(basePath, tree);
242
+ return tree;
243
+ }
244
+ async function createCanonicalSnapshot(instance, options) {
245
+ const files = [];
246
+ let totalBytes = 0;
247
+ const tree = await readFileTree(instance, "/", options);
248
+ function flatten(t, currentPath) {
249
+ for (const [name, node] of Object.entries(t)) {
250
+ const fullPath = currentPath === "/" ? `/${name}` : `${currentPath}/${name}`;
251
+ if ("file" in node && node.file && "contents" in node.file) {
252
+ const content = node.file.contents;
253
+ const byteSize = new TextEncoder().encode(content).byteLength;
254
+ if (byteSize > MAX_FILE_BYTES) {
255
+ throw new Error(`Snapshot rejected: file "${fullPath}" is ${(byteSize / 1024 / 1024).toFixed(1)}MB, ` + `exceeding the ${(MAX_FILE_BYTES / 1024 / 1024).toFixed(0)}MB per-file limit.`);
256
+ }
257
+ totalBytes += byteSize;
258
+ files.push({ path: fullPath, content, byteSize });
259
+ } else if ("directory" in node && node.directory) {
260
+ flatten(node.directory, fullPath);
261
+ }
262
+ }
263
+ }
264
+ flatten(tree, "/");
265
+ files.sort((a, b) => a.path.localeCompare(b.path));
266
+ if (files.length > MAX_SNAPSHOT_FILES) {
267
+ throw new Error(`Snapshot rejected: ${files.length} files exceeds the ${MAX_SNAPSHOT_FILES} file cap.`);
268
+ }
269
+ if (totalBytes > MAX_SNAPSHOT_BYTES) {
270
+ throw new Error(`Snapshot rejected: ${(totalBytes / 1024 / 1024).toFixed(1)}MB exceeds the ` + `${(MAX_SNAPSHOT_BYTES / 1024 / 1024).toFixed(0)}MB uncompressed size cap.`);
271
+ }
272
+ const canonical = files.map((f) => `${f.path}\x00${f.content}`).join("\x00\x00");
273
+ const encoded = new TextEncoder().encode(canonical);
274
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
275
+ const treeHash = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
276
+ return {
277
+ files,
278
+ fileCount: files.length,
279
+ totalBytes,
280
+ treeHash
281
+ };
282
+ }
283
+ function snapshotToFileSystemTree(snapshot) {
284
+ const tree = {};
285
+ for (const file of snapshot.files) {
286
+ const segments = file.path.split("/").filter(Boolean);
287
+ let current = tree;
288
+ for (let i = 0;i < segments.length - 1; i++) {
289
+ const seg = segments[i];
290
+ if (!(seg in current)) {
291
+ current[seg] = { directory: {} };
292
+ }
293
+ const node = current[seg];
294
+ if (node && typeof node === "object" && "directory" in node && node.directory) {
295
+ current = node.directory;
296
+ }
297
+ }
298
+ const filename = segments[segments.length - 1];
299
+ current[filename] = { file: { contents: file.content } };
300
+ }
301
+ return tree;
302
+ }
303
+ async function serializeProject(instance) {
304
+ const tree = await readFileTree(instance);
305
+ const result = [];
306
+ function flatten(t, currentPath) {
307
+ for (const [name, node] of Object.entries(t)) {
308
+ const fullPath = currentPath === "/" ? `/${name}` : `${currentPath}/${name}`;
309
+ if ("file" in node && node.file && "contents" in node.file) {
310
+ result.push({ path: fullPath, content: node.file.contents });
311
+ } else if ("directory" in node && node.directory) {
312
+ flatten(node.directory, fullPath);
313
+ }
314
+ }
315
+ }
316
+ flatten(tree, "/");
317
+ return result;
318
+ }
319
+ async function writeFileTree(instance, _path, tree) {
320
+ await instance.mount(tree);
321
+ }
322
+
323
+ class WriteCoalescer {
324
+ instance;
325
+ pending = new Map;
326
+ timer = null;
327
+ dirty = false;
328
+ maxBufferSize;
329
+ currentBufferSize = 0;
330
+ onFlush;
331
+ writeListeners = new Set;
332
+ deleteListeners = new Set;
333
+ constructor(instance, options) {
334
+ this.instance = instance;
335
+ this.maxBufferSize = options?.maxBufferSize ?? 5 * 1024 * 1024;
336
+ this.onFlush = options?.onFlush;
337
+ }
338
+ onWrite(listener) {
339
+ this.writeListeners.add(listener);
340
+ return () => {
341
+ this.writeListeners.delete(listener);
342
+ };
343
+ }
344
+ onDelete(listener) {
345
+ this.deleteListeners.add(listener);
346
+ return () => {
347
+ this.deleteListeners.delete(listener);
348
+ };
349
+ }
350
+ notifyDelete(path) {
351
+ for (const listener of this.deleteListeners) {
352
+ try {
353
+ listener(path);
354
+ } catch {}
355
+ }
356
+ }
357
+ enqueue(path, content) {
358
+ const size = new Blob([content]).size;
359
+ if (this.currentBufferSize + size > this.maxBufferSize) {
360
+ const files = Array.from(this.pending.entries()).map(([k, v]) => ({
361
+ path: k,
362
+ content: v
363
+ }));
364
+ this.flushImmediate(files);
365
+ if (size > this.maxBufferSize) {
366
+ console.warn("WriteCoalescer: single file exceeds buffer capacity, skipping coalesce.");
367
+ this.writeDirect(path, content);
368
+ return;
369
+ }
370
+ }
371
+ this.pending.set(path, content);
372
+ this.currentBufferSize += size;
373
+ this.dirty = true;
374
+ this.scheduleFlush();
375
+ }
376
+ write(path, content) {
377
+ this.enqueue(path, content);
378
+ }
379
+ scheduleFlush(idleMs = 150) {
380
+ if (this.timer !== null)
381
+ window.clearTimeout(this.timer);
382
+ this.timer = window.setTimeout(() => {
383
+ const files = Array.from(this.pending.entries()).map(([k, v]) => ({
384
+ path: k,
385
+ content: v
386
+ }));
387
+ this.flushImmediate(files);
388
+ }, idleMs);
389
+ }
390
+ async flushImmediate(files) {
391
+ if (this.timer !== null) {
392
+ window.clearTimeout(this.timer);
393
+ this.timer = null;
394
+ }
395
+ if (files.length === 0)
396
+ return;
397
+ this.currentBufferSize = 0;
398
+ for (const file of files) {
399
+ await this.writeDirect(file.path, file.content);
400
+ this.pending.delete(file.path);
401
+ for (const listener of this.writeListeners) {
402
+ try {
403
+ listener(file.path);
404
+ } catch {}
405
+ }
406
+ }
407
+ this.dirty = false;
408
+ this.onFlush?.();
409
+ }
410
+ async writeDirect(path, content) {
411
+ const parts = path.split("/");
412
+ let p = "";
413
+ for (let i = 0;i < parts.length - 1; i++) {
414
+ if (!parts[i])
415
+ continue;
416
+ p += "/" + parts[i];
417
+ try {
418
+ await this.instance.fs.mkdir(p);
419
+ } catch {}
420
+ }
421
+ await this.instance.fs.writeFile(path, content);
422
+ }
423
+ destroy() {
424
+ if (this.timer !== null)
425
+ window.clearTimeout(this.timer);
426
+ this.writeListeners.clear();
427
+ this.deleteListeners.clear();
428
+ }
429
+ }
430
+ var init_filesystem = __esm(() => {
431
+ init_path_policy();
432
+ init_path_policy();
433
+ });
434
+
435
+ // src/core/process-registry.ts
436
+ class ProcessRegistry {
437
+ processes = new Map;
438
+ outputListeners = new Set;
439
+ processEventListeners = new Set;
440
+ instance;
441
+ constructor(instance) {
442
+ this.instance = instance;
443
+ }
444
+ isRunning(key) {
445
+ return this.processes.has(key);
446
+ }
447
+ get(key) {
448
+ return this.processes.get(key) ?? null;
449
+ }
450
+ getAll() {
451
+ return Array.from(this.processes.values());
452
+ }
453
+ async spawn(key, command, args, options) {
454
+ if (this.processes.has(key)) {
455
+ throw new Error(`Process "${key}" is already running. Kill it first or use a different key.`);
456
+ }
457
+ const maxOutput = options?.maxOutput ?? 2 * 1024 * 1024;
458
+ const timeout = options?.timeout ?? 0;
459
+ const proc = await this.instance.spawn(command, args);
460
+ let isKilled = false;
461
+ let totalOutput = 0;
462
+ let outputBuffer = "";
463
+ let rafScheduled = false;
464
+ const flushBuffer = () => {
465
+ if (outputBuffer.length > 0 && !isKilled) {
466
+ const chunk = outputBuffer;
467
+ outputBuffer = "";
468
+ this.emitOutput(key, chunk);
469
+ }
470
+ rafScheduled = false;
471
+ };
472
+ const stream = new WritableStream({
473
+ write: (data) => {
474
+ if (isKilled)
475
+ return;
476
+ totalOutput += data.length;
477
+ if (totalOutput > maxOutput) {
478
+ this.emitOutput(key, `
479
+ --- Output limit reached, killing process ---
480
+ `);
481
+ isKilled = true;
482
+ proc.kill();
483
+ return;
484
+ }
485
+ outputBuffer += data;
486
+ if (outputBuffer.length > 50 * 1024) {
487
+ flushBuffer();
488
+ return;
489
+ }
490
+ if (!rafScheduled) {
491
+ rafScheduled = true;
492
+ requestAnimationFrame(flushBuffer);
493
+ }
494
+ }
495
+ });
496
+ proc.output.pipeTo(stream).catch(() => {});
497
+ let timeoutId;
498
+ if (timeout > 0) {
499
+ timeoutId = window.setTimeout(() => {
500
+ if (!isKilled) {
501
+ isKilled = true;
502
+ this.emitOutput(key, `
503
+ --- Process "${key}" timed out after ${timeout}ms ---
504
+ `);
505
+ proc.kill();
506
+ }
507
+ }, timeout);
508
+ }
509
+ const exitCode = new Promise((resolve) => {
510
+ proc.exit.then((code) => {
511
+ if (timeoutId)
512
+ window.clearTimeout(timeoutId);
513
+ if (outputBuffer.length > 0 && !isKilled) {
514
+ this.emitOutput(key, outputBuffer);
515
+ outputBuffer = "";
516
+ }
517
+ this.processes.delete(key);
518
+ this.emitProcessEvent(key, null);
519
+ resolve(isKilled ? 143 : code);
520
+ });
521
+ });
522
+ const entry = {
523
+ key,
524
+ command,
525
+ args,
526
+ exitCode,
527
+ kill: () => {
528
+ isKilled = true;
529
+ proc.kill();
530
+ },
531
+ startedAt: Date.now()
532
+ };
533
+ this.processes.set(key, entry);
534
+ this.emitProcessEvent(key, entry);
535
+ this.emitOutput(key, `
536
+ \x1B[90m$ ${command} ${args.join(" ")}\x1B[0m
537
+ `);
538
+ return entry;
539
+ }
540
+ kill(key) {
541
+ const entry = this.processes.get(key);
542
+ if (entry) {
543
+ entry.kill();
544
+ }
545
+ }
546
+ killAll() {
547
+ for (const entry of this.processes.values()) {
548
+ entry.kill();
549
+ }
550
+ }
551
+ onOutput(listener) {
552
+ this.outputListeners.add(listener);
553
+ return () => {
554
+ this.outputListeners.delete(listener);
555
+ };
556
+ }
557
+ onProcessEvent(listener) {
558
+ this.processEventListeners.add(listener);
559
+ return () => {
560
+ this.processEventListeners.delete(listener);
561
+ };
562
+ }
563
+ writeSystemMessage(message) {
564
+ this.emitOutput("system", `\x1B[90m${message}\x1B[0m
565
+ `);
566
+ }
567
+ emitOutput(key, chunk) {
568
+ for (const listener of this.outputListeners) {
569
+ try {
570
+ listener(key, chunk);
571
+ } catch {}
572
+ }
573
+ }
574
+ emitProcessEvent(key, entry) {
575
+ for (const listener of this.processEventListeners) {
576
+ try {
577
+ listener(key, entry);
578
+ } catch {}
579
+ }
580
+ }
581
+ destroy() {
582
+ this.killAll();
583
+ this.outputListeners.clear();
584
+ this.processEventListeners.clear();
585
+ }
586
+ }
587
+
588
+ // src/templates/react-template.generated.ts
589
+ var REACT_TEMPLATE;
590
+ var init_react_template_generated = __esm(() => {
591
+ REACT_TEMPLATE = {
592
+ "index.html": `<!doctype html>
593
+ <html lang="en">
594
+ <head>
595
+ <meta charset="UTF-8" />
596
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
597
+ <title><%= it.projectName %> — Dev Sandbox</title>
598
+ <style>
599
+ body {
600
+ margin: 0;
601
+ padding-top: 40px;
602
+ }
603
+ #dev-toolbar {
604
+ position: fixed;
605
+ top: 0;
606
+ left: 0;
607
+ right: 0;
608
+ height: 40px;
609
+ background: #1a1a2e;
610
+ display: flex;
611
+ align-items: center;
612
+ padding: 0 16px;
613
+ gap: 16px;
614
+ z-index: 99999;
615
+ font-family: system-ui, -apple-system, sans-serif;
616
+ font-size: 13px;
617
+ }
618
+ #dev-toolbar .toolbar-label {
619
+ color: #a78bfa;
620
+ font-weight: 600;
621
+ letter-spacing: 0.02em;
622
+ }
623
+ #dev-toolbar .toolbar-hint {
624
+ color: #6b7280;
625
+ margin-left: auto;
626
+ }
627
+ #dev-toolbar button {
628
+ background: #2d2d44;
629
+ color: #e5e7eb;
630
+ border: 1px solid #3f3f5c;
631
+ border-radius: 4px;
632
+ padding: 4px 12px;
633
+ cursor: pointer;
634
+ font-size: 13px;
635
+ font-family: inherit;
636
+ }
637
+ #dev-toolbar button:hover {
638
+ background: #3f3f5c;
639
+ }
640
+ </style>
641
+ </head>
642
+ <body>
643
+ <div id="dev-toolbar">
644
+ <span class="toolbar-label">Dev Sandbox</span>
645
+ <button id="theme-toggle">Theme: light</button>
646
+ <span class="toolbar-hint">Bridge calls logged to console</span>
647
+ </div>
648
+ <<%= it.projectName %> id="app"></<%= it.projectName %>>
649
+ <script type="module">
650
+ import "./src/main.tsx";
651
+ import { mockBridge } from "./src/mock-bridge.ts";
652
+
653
+ let currentTheme = "light";
654
+ const app = document.getElementById("app");
655
+ app.bridge = mockBridge;
656
+
657
+ document.getElementById("theme-toggle").addEventListener("click", () => {
658
+ currentTheme = currentTheme === "light" ? "dark" : "light";
659
+ document.getElementById("theme-toggle").textContent = \`Theme: \${currentTheme}\`;
660
+
661
+ const original = mockBridge.theme.current;
662
+ mockBridge.theme.current = () => currentTheme;
663
+
664
+ if (mockBridge.events && mockBridge.events.emit) {
665
+ mockBridge.events.emit("theme-changed", { theme: currentTheme });
666
+ }
667
+
668
+ // Re-assign bridge to trigger re-render
669
+ app.bridge = mockBridge;
670
+ });
671
+ </script>
672
+ </body>
673
+ </html>
674
+ `,
675
+ "manifest.json": `{
676
+ "id": "<%= it.projectName %>",
677
+ "version": "0.0.1",
678
+ "framework": "react",
679
+ "permissions": [],
680
+ "description": "A vibe-coded mini-app."
681
+ }
682
+ `,
683
+ "package.json": `{
684
+ "name": "<%= it.projectName %>",
685
+ "private": true,
686
+ "version": "0.0.1",
687
+ "type": "module",
688
+ "scripts": {
689
+ "dev": "vite",
690
+ "build": "vite build",
691
+ "preview": "vite preview",
692
+ "validate": "vibe-kit validate"
693
+ },
694
+ "dependencies": {
695
+ "react": "^19.0.0",
696
+ "react-dom": "^19.0.0",
697
+ "@aiworkbench/vibe-types": "^0.0.4"
698
+ },
699
+ "devDependencies": {
700
+ "@vitejs/plugin-react": "^4.3.0",
701
+ "@types/react": "^19.0.0",
702
+ "@types/react-dom": "^19.0.0",
703
+ "typescript": "^5.7.0",
704
+ "vite": "^6.0.0"
705
+ }
706
+ }
707
+ `,
708
+ "src/App.tsx": `import type { VibeProps } from "@aiworkbench/vibe-types";
709
+
710
+ export default function App({ bridge }: VibeProps) {
711
+ const user = bridge.auth?.getUser();
712
+
713
+ return (
714
+ <div style={{ padding: "2rem", fontFamily: "system-ui" }}>
715
+ <h1>Hello, {user?.name ?? "Developer"}!</h1>
716
+ <p>This is your vibe-coded mini-app.</p>
717
+ <button onClick={() => bridge.toast?.show("It works!")}>
718
+ Show Toast
719
+ </button>
720
+ </div>
721
+ );
722
+ }
723
+ `,
724
+ "src/main.tsx": `import React from "react";
725
+ import ReactDOM from "react-dom/client";
726
+ import App from "./App";
727
+
728
+ class VibeElement extends HTMLElement {
729
+ private _bridge: any;
730
+ private _root: ReactDOM.Root | null = null;
731
+
732
+ set bridge(value: any) {
733
+ this._bridge = value;
734
+ this.render();
735
+ }
736
+
737
+ connectedCallback() {
738
+ const shadow = this.attachShadow({ mode: "open" });
739
+ const container = document.createElement("div");
740
+ shadow.appendChild(container);
741
+ this._root = ReactDOM.createRoot(container);
742
+ this.render();
743
+ }
744
+
745
+ disconnectedCallback() {
746
+ this._root?.unmount();
747
+ }
748
+
749
+ private render() {
750
+ if (this._root && this._bridge) {
751
+ this._root.render(React.createElement(App, { bridge: this._bridge }));
752
+ }
753
+ }
754
+ }
755
+
756
+ if (!customElements.get("<%= it.projectName %>")) {
757
+ customElements.define("<%= it.projectName %>", VibeElement);
758
+ }
759
+
760
+ if (import.meta.env.DEV) {
761
+ const mountDevBridge = () => {
762
+ const bridge = (window as any).__VIBE_BRIDGE_DEV__;
763
+ if (bridge) {
764
+ let rootNode = document.getElementById("root");
765
+ if (!rootNode) {
766
+ rootNode = document.createElement("div");
767
+ rootNode.id = "root";
768
+ document.body.appendChild(rootNode);
769
+ }
770
+ if (rootNode.children.length === 0) {
771
+ const el = document.createElement("<%= it.projectName %>") as any;
772
+ el.bridge = bridge;
773
+ rootNode.appendChild(el);
774
+ }
775
+ }
776
+ };
777
+
778
+ if ((window as any).__VIBE_BRIDGE_DEV__) {
779
+ mountDevBridge();
780
+ } else {
781
+ window.addEventListener("vibe-bridge-ready", mountDevBridge);
782
+ }
783
+ }
784
+ `,
785
+ "tsconfig.json": `{
786
+ "compilerOptions": {
787
+ "target": "es2022",
788
+ "module": "es2022",
789
+ "moduleResolution": "bundler",
790
+ "jsx": "react-jsx",
791
+ "strict": true,
792
+ "esModuleInterop": true,
793
+ "skipLibCheck": true,
794
+ "resolveJsonModule": true
795
+ },
796
+ "include": ["src"]
797
+ }
798
+ `,
799
+ "vite.config.ts": `import { defineConfig } from "vite";
800
+ import react from "@vitejs/plugin-react";
801
+
802
+ export default defineConfig({
803
+ plugins: [react()],
804
+ define: {
805
+ "process.env.NODE_ENV": JSON.stringify("production"),
806
+ },
807
+ build: {
808
+ lib: {
809
+ entry: "./src/main.tsx",
810
+ name: "<%= it.projectNamePascal %>",
811
+ fileName: "index",
812
+ formats: ["es"],
813
+ },
814
+ rollupOptions: {
815
+ external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime"],
816
+ output: {
817
+ globals: {
818
+ react: "React",
819
+ "react-dom": "ReactDOM",
820
+ },
821
+ },
822
+ },
823
+ },
824
+ });
825
+ `
826
+ };
827
+ });
828
+
829
+ // src/templates/generated.ts
830
+ function toPascalCase(name) {
831
+ return name.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase());
832
+ }
833
+ function resolveVars(templateId, vars) {
834
+ const projectName = vars?.projectName ?? `vibe-${templateId}-app`;
835
+ const projectNamePascal = vars?.projectNamePascal ?? toPascalCase(projectName);
836
+ return {
837
+ projectName,
838
+ projectNamePascal,
839
+ permissions: vars?.permissions,
840
+ description: vars?.description
841
+ };
842
+ }
843
+ function renderEta(template, vars) {
844
+ return template.replace(/<%= it\.projectName %>/g, vars.projectName).replace(/<%= it\.projectNamePascal %>/g, vars.projectNamePascal);
845
+ }
846
+ function buildIdeIndexHtml(templateId) {
847
+ const mainEntry = templateId === "react" ? "/src/main.tsx" : "/src/main.ts";
848
+ return `<!doctype html>
849
+ <html lang="en">
850
+ <head>
851
+ <meta charset="UTF-8" />
852
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
853
+ <title>Vibe IDE Draft</title>
854
+ </head>
855
+ <body style="margin: 0;">
856
+ <div id="root"></div>
857
+ <script type="module" src="${mainEntry}"></script>
858
+ </body>
859
+ </html>`;
860
+ }
861
+ function injectBridgePlugin(viteConfig) {
862
+ const pluginsMatch = viteConfig.match(/plugins:\s*\[([^\]]+)\]/s);
863
+ if (pluginsMatch?.[1]) {
864
+ const existing = pluginsMatch[1].trimEnd();
865
+ const normalized = existing.endsWith(",") ? existing : existing + ",";
866
+ return viteConfig.replace(pluginsMatch[0], `plugins: [
867
+ ${normalized.trim()}
868
+ ${BRIDGE_INJECTOR_PLUGIN}
869
+ ]`);
870
+ }
871
+ return viteConfig.replace(/defineConfig\(\{/, `defineConfig({
872
+ plugins: [
873
+ ${BRIDGE_INJECTOR_PLUGIN}
874
+ ],`);
875
+ }
876
+ function overrideManifestPermissions(rendered, permissions) {
877
+ const manifest = JSON.parse(rendered);
878
+ manifest.permissions = permissions;
879
+ return JSON.stringify(manifest, null, 2);
880
+ }
881
+ function toFileSystemTree(files) {
882
+ const tree = {};
883
+ for (const [path, content] of Object.entries(files)) {
884
+ const parts = path.split("/");
885
+ if (parts.length === 1) {
886
+ tree[path] = { file: { contents: content } };
887
+ } else {
888
+ let current = tree;
889
+ for (let i = 0;i < parts.length - 1; i++) {
890
+ const dir = parts[i];
891
+ if (!current[dir]) {
892
+ current[dir] = { directory: {} };
893
+ }
894
+ current = current[dir].directory;
895
+ }
896
+ current[parts[parts.length - 1]] = { file: { contents: content } };
897
+ }
898
+ }
899
+ return tree;
900
+ }
901
+ function buildReactTree(vars) {
902
+ const rendered = {};
903
+ for (const [path, content] of Object.entries(REACT_TEMPLATE)) {
904
+ rendered[path] = renderEta(content, vars);
905
+ }
906
+ rendered["index.html"] = buildIdeIndexHtml("react");
907
+ rendered["vite.config.ts"] = injectBridgePlugin(rendered["vite.config.ts"]);
908
+ rendered["manifest.json"] = overrideManifestPermissions(rendered["manifest.json"], vars.permissions ?? ["toast"]);
909
+ return toFileSystemTree(rendered);
910
+ }
911
+ function buildVueTree(vars) {
912
+ const vueApp = `<script setup lang="ts">
913
+ import type { Bridge } from "@aiworkbench/vibe-types";
914
+
915
+ const props = defineProps<{ bridge: Bridge }>();
916
+
917
+ const user = props.bridge.auth?.getUser();
918
+ </script>
919
+
920
+ <template>
921
+ <div style="padding: 2rem; font-family: system-ui">
922
+ <h1>Hello, {{ user?.name ?? "Developer" }}!</h1>
923
+ <p>This is your vibe-coded mini-app.</p>
924
+ <button @click="props.bridge.toast?.show('It works!')">Show Toast</button>
925
+ </div>
926
+ </template>
927
+ `;
928
+ const vueMain = `import { defineCustomElement } from "vue";
929
+ import App from "./App.vue";
930
+
931
+ const VibeElement = defineCustomElement(App, {
932
+ shadowRoot: true,
933
+ });
934
+
935
+ if (!customElements.get("${vars.projectName}")) {
936
+ customElements.define("${vars.projectName}", VibeElement);
937
+ }
938
+
939
+ if (import.meta.env.DEV) {
940
+ const mountDevBridge = () => {
941
+ const bridge = (window as any).__VIBE_BRIDGE_DEV__;
942
+ if (bridge) {
943
+ let rootNode = document.getElementById("root");
944
+ if (!rootNode) {
945
+ rootNode = document.createElement("div");
946
+ rootNode.id = "root";
947
+ document.body.appendChild(rootNode);
948
+ }
949
+ if (rootNode.children.length === 0) {
950
+ const el = document.createElement("${vars.projectName}") as any;
951
+ el.bridge = bridge;
952
+ rootNode.appendChild(el);
953
+ }
954
+ }
955
+ };
956
+
957
+ if ((window as any).__VIBE_BRIDGE_DEV__) {
958
+ mountDevBridge();
959
+ } else {
960
+ window.addEventListener("vibe-bridge-ready", mountDevBridge);
961
+ }
962
+ }
963
+ `;
964
+ const viteConfig = `import { defineConfig } from "vite";
965
+ import vue from "@vitejs/plugin-vue";
966
+
967
+ export default defineConfig({
968
+ plugins: [
969
+ vue(),
970
+ {
971
+ name: "vibe-dev-bridge-injector",
972
+ apply: "serve",
973
+ transformIndexHtml(html) {
974
+ return html.replace(
975
+ "</head>",
976
+ ' <script type="module" src="/.ide/vibe-bridge-dev.js"></script>\\n </head>',
977
+ );
978
+ },
979
+ },
980
+ ],
981
+ define: {
982
+ "process.env.NODE_ENV": JSON.stringify("production"),
983
+ },
984
+ build: {
985
+ lib: {
986
+ entry: "./src/main.ts",
987
+ name: "${vars.projectNamePascal}",
988
+ fileName: "index",
989
+ formats: ["es"],
990
+ },
991
+ rollupOptions: {
992
+ external: ["vue"],
993
+ output: {
994
+ globals: {
995
+ vue: "Vue",
996
+ },
997
+ },
998
+ },
999
+ },
1000
+ });
1001
+ `;
1002
+ return {
1003
+ "package.json": {
1004
+ file: {
1005
+ contents: JSON.stringify({
1006
+ name: vars.projectName,
1007
+ private: true,
1008
+ version: "0.0.1",
1009
+ type: "module",
1010
+ scripts: { dev: "vite", build: "vite build", preview: "vite preview", validate: "vibe-kit validate" },
1011
+ dependencies: { vue: "^3.5.0", "@aiworkbench/vibe-types": "^0.0.4" },
1012
+ devDependencies: { "@vitejs/plugin-vue": "^5.2.0", typescript: "^5.7.0", vite: "^6.0.0" }
1013
+ }, null, 2)
1014
+ }
1015
+ },
1016
+ "manifest.json": {
1017
+ file: {
1018
+ contents: JSON.stringify({ id: vars.projectName, version: "0.0.1", framework: "vue", permissions: vars.permissions ?? ["toast"], description: vars.description ?? "A vibe-coded mini-app." }, null, 2)
1019
+ }
1020
+ },
1021
+ "index.html": { file: { contents: buildIdeIndexHtml("vue") } },
1022
+ "vite.config.ts": { file: { contents: viteConfig } },
1023
+ "tsconfig.json": {
1024
+ file: {
1025
+ contents: JSON.stringify({ compilerOptions: { target: "es2022", module: "es2022", moduleResolution: "bundler", strict: true, esModuleInterop: true, skipLibCheck: true, resolveJsonModule: true }, include: ["src"] }, null, 2)
1026
+ }
1027
+ },
1028
+ src: {
1029
+ directory: {
1030
+ "App.vue": { file: { contents: vueApp } },
1031
+ "main.ts": { file: { contents: vueMain } }
1032
+ }
1033
+ }
1034
+ };
1035
+ }
1036
+ function buildVanillaTree(vars) {
1037
+ const mainTs = `import type { Bridge } from "@aiworkbench/vibe-types";
1038
+
1039
+ class VibeElement extends HTMLElement {
1040
+ private _bridge: Bridge | null = null;
1041
+ private _shadow: ShadowRoot;
1042
+
1043
+ constructor() {
1044
+ super();
1045
+ this._shadow = this.attachShadow({ mode: "open" });
1046
+ }
1047
+
1048
+ set bridge(value: Bridge) {
1049
+ this._bridge = value;
1050
+ this.render();
1051
+ }
1052
+
1053
+ connectedCallback() {
1054
+ this.render();
1055
+ }
1056
+
1057
+ private render() {
1058
+ if (!this._bridge) return;
1059
+
1060
+ const user = this._bridge.auth?.getUser();
1061
+ const bridge = this._bridge;
1062
+
1063
+ this._shadow.innerHTML = \`
1064
+ <style>
1065
+ :host {
1066
+ display: block;
1067
+ font-family: system-ui, sans-serif;
1068
+ padding: 2rem;
1069
+ }
1070
+ button {
1071
+ cursor: pointer;
1072
+ padding: 0.5rem 1rem;
1073
+ border: 1px solid #ccc;
1074
+ border-radius: 4px;
1075
+ background: #fff;
1076
+ }
1077
+ button:hover {
1078
+ background: #f0f0f0;
1079
+ }
1080
+ </style>
1081
+ <h1>Hello, <span id="user-name"></span>!</h1>
1082
+ <p>This is your vibe-coded mini-app.</p>
1083
+ <button id="toast-btn">Show Toast</button>
1084
+ \`;
1085
+
1086
+ const nameEl = this._shadow.getElementById("user-name");
1087
+ if (nameEl) nameEl.textContent = user?.name ?? "Developer";
1088
+
1089
+ this._shadow
1090
+ .getElementById("toast-btn")
1091
+ ?.addEventListener("click", () => bridge.toast?.show("It works!"));
1092
+ }
1093
+ }
1094
+
1095
+ if (!customElements.get("${vars.projectName}")) {
1096
+ customElements.define("${vars.projectName}", VibeElement);
1097
+ }
1098
+
1099
+ if (import.meta.env.DEV) {
1100
+ const mountDevBridge = () => {
1101
+ const bridge = (window as any).__VIBE_BRIDGE_DEV__;
1102
+ if (bridge) {
1103
+ let rootNode = document.getElementById("root");
1104
+ if (!rootNode) {
1105
+ rootNode = document.createElement("div");
1106
+ rootNode.id = "root";
1107
+ document.body.appendChild(rootNode);
1108
+ }
1109
+ if (rootNode.children.length === 0) {
1110
+ const el = document.createElement("${vars.projectName}") as any;
1111
+ el.bridge = bridge;
1112
+ rootNode.appendChild(el);
1113
+ }
1114
+ }
1115
+ };
1116
+
1117
+ if ((window as any).__VIBE_BRIDGE_DEV__) {
1118
+ mountDevBridge();
1119
+ } else {
1120
+ window.addEventListener("vibe-bridge-ready", mountDevBridge);
1121
+ }
1122
+ }
1123
+ `;
1124
+ const viteConfig = `import { defineConfig } from "vite";
1125
+
1126
+ export default defineConfig({
1127
+ plugins: [
1128
+ {
1129
+ name: "vibe-dev-bridge-injector",
1130
+ apply: "serve",
1131
+ transformIndexHtml(html) {
1132
+ return html.replace(
1133
+ "</head>",
1134
+ ' <script type="module" src="/.ide/vibe-bridge-dev.js"></script>\\n </head>',
1135
+ );
1136
+ },
1137
+ },
1138
+ ],
1139
+ define: {
1140
+ "process.env.NODE_ENV": JSON.stringify("production"),
1141
+ },
1142
+ build: {
1143
+ lib: {
1144
+ entry: "./src/main.ts",
1145
+ name: "${vars.projectNamePascal}",
1146
+ fileName: "index",
1147
+ formats: ["es"],
1148
+ },
1149
+ },
1150
+ });
1151
+ `;
1152
+ return {
1153
+ "package.json": {
1154
+ file: {
1155
+ contents: JSON.stringify({
1156
+ name: vars.projectName,
1157
+ private: true,
1158
+ version: "0.0.1",
1159
+ type: "module",
1160
+ scripts: { dev: "vite", build: "vite build", preview: "vite preview", validate: "vibe-kit validate" },
1161
+ dependencies: { "@aiworkbench/vibe-types": "^0.0.4" },
1162
+ devDependencies: { typescript: "^5.7.0", vite: "^6.0.0" }
1163
+ }, null, 2)
1164
+ }
1165
+ },
1166
+ "manifest.json": {
1167
+ file: {
1168
+ contents: JSON.stringify({ id: vars.projectName, version: "0.0.1", framework: "vanilla", permissions: vars.permissions ?? ["toast"], description: vars.description ?? "A vibe-coded mini-app." }, null, 2)
1169
+ }
1170
+ },
1171
+ "index.html": { file: { contents: buildIdeIndexHtml("vanilla") } },
1172
+ "vite.config.ts": { file: { contents: viteConfig } },
1173
+ "tsconfig.json": {
1174
+ file: {
1175
+ contents: JSON.stringify({ compilerOptions: { target: "es2022", module: "es2022", moduleResolution: "bundler", strict: true, esModuleInterop: true, skipLibCheck: true, resolveJsonModule: true }, include: ["src"] }, null, 2)
1176
+ }
1177
+ },
1178
+ src: {
1179
+ directory: {
1180
+ "main.ts": { file: { contents: mainTs } }
1181
+ }
1182
+ }
1183
+ };
1184
+ }
1185
+ function getVibeIdeTemplateTree(templateId, templateVars) {
1186
+ const vars = resolveVars(templateId, templateVars);
1187
+ switch (templateId) {
1188
+ case "react":
1189
+ return buildReactTree(vars);
1190
+ case "vue":
1191
+ return buildVueTree(vars);
1192
+ case "vanilla":
1193
+ return buildVanillaTree(vars);
1194
+ }
1195
+ }
1196
+ var BRIDGE_INJECTOR_PLUGIN = ` {
1197
+ name: "vibe-dev-bridge-injector",
1198
+ apply: "serve",
1199
+ transformIndexHtml(html) {
1200
+ return html.replace(
1201
+ "</head>",
1202
+ ' <script type="module" src="/.ide/vibe-bridge-dev.js"></script>\\n </head>',
1203
+ );
1204
+ },
1205
+ },`;
1206
+ var init_generated = __esm(() => {
1207
+ init_react_template_generated();
1208
+ });
1209
+
1210
+ // src/core/local-snapshot-cache.ts
1211
+ function openDB() {
1212
+ if (dbPromise)
1213
+ return dbPromise;
1214
+ dbPromise = new Promise((resolve, reject) => {
1215
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
1216
+ request.onupgradeneeded = () => {
1217
+ const db = request.result;
1218
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
1219
+ const store = db.createObjectStore(STORE_NAME, { keyPath: "draftId" });
1220
+ store.createIndex("savedAt", "savedAt", { unique: false });
1221
+ }
1222
+ };
1223
+ request.onsuccess = () => {
1224
+ const db = request.result;
1225
+ evictStaleSnapshots(db).catch(() => {});
1226
+ resolve(db);
1227
+ };
1228
+ request.onerror = () => {
1229
+ dbPromise = null;
1230
+ reject(request.error);
1231
+ };
1232
+ request.onblocked = () => {
1233
+ dbPromise = null;
1234
+ reject(new Error("IndexedDB blocked — close other tabs using the IDE"));
1235
+ };
1236
+ });
1237
+ return dbPromise;
1238
+ }
1239
+ function withStore(mode, fn) {
1240
+ return openDB().then((db) => new Promise((resolve, reject) => {
1241
+ const tx = db.transaction(STORE_NAME, mode);
1242
+ const store = tx.objectStore(STORE_NAME);
1243
+ const req = fn(store);
1244
+ req.onsuccess = () => resolve(req.result);
1245
+ req.onerror = () => reject(req.error);
1246
+ }));
1247
+ }
1248
+ async function saveLocalSnapshot(draftId, tree, meta) {
1249
+ const record = {
1250
+ draftId,
1251
+ tree,
1252
+ treeHash: meta.treeHash,
1253
+ installFingerprint: meta.installFingerprint,
1254
+ revisionId: meta.revisionId,
1255
+ chatHistory: meta.chatHistory,
1256
+ savedAt: Date.now()
1257
+ };
1258
+ await withStore("readwrite", (store) => store.put(record));
1259
+ }
1260
+ async function loadLocalSnapshot(draftId) {
1261
+ const result = await withStore("readonly", (store) => store.get(draftId));
1262
+ return result ?? null;
1263
+ }
1264
+ async function deleteLocalSnapshot(draftId) {
1265
+ await withStore("readwrite", (store) => store.delete(draftId));
1266
+ }
1267
+ async function hasNewerLocalSnapshot(draftId, remoteSavedAt) {
1268
+ const record = await loadLocalSnapshot(draftId);
1269
+ if (!record)
1270
+ return false;
1271
+ const remoteTs = typeof remoteSavedAt === "string" ? new Date(remoteSavedAt).getTime() : remoteSavedAt;
1272
+ return record.savedAt > remoteTs;
1273
+ }
1274
+ async function getLocalSnapshotTimestamp(draftId) {
1275
+ const record = await loadLocalSnapshot(draftId);
1276
+ return record?.savedAt ?? null;
1277
+ }
1278
+ async function listLocalSnapshots() {
1279
+ const db = await openDB();
1280
+ return new Promise((resolve, reject) => {
1281
+ const tx = db.transaction(STORE_NAME, "readonly");
1282
+ const store = tx.objectStore(STORE_NAME);
1283
+ const req = store.getAll();
1284
+ req.onsuccess = () => {
1285
+ const records = req.result;
1286
+ resolve(records.map((r) => ({
1287
+ draftId: r.draftId,
1288
+ savedAt: r.savedAt,
1289
+ treeHash: r.treeHash
1290
+ })));
1291
+ };
1292
+ req.onerror = () => reject(req.error);
1293
+ });
1294
+ }
1295
+ async function evictStaleSnapshots(db) {
1296
+ const cutoff = Date.now() - MAX_AGE_MS;
1297
+ return new Promise((resolve, reject) => {
1298
+ const tx = db.transaction(STORE_NAME, "readwrite");
1299
+ const store = tx.objectStore(STORE_NAME);
1300
+ const index = store.index("savedAt");
1301
+ const range = IDBKeyRange.upperBound(cutoff);
1302
+ const req = index.openCursor(range);
1303
+ req.onsuccess = () => {
1304
+ const cursor = req.result;
1305
+ if (cursor) {
1306
+ cursor.delete();
1307
+ cursor.continue();
1308
+ }
1309
+ };
1310
+ tx.oncomplete = () => resolve();
1311
+ tx.onerror = () => reject(tx.error);
1312
+ });
1313
+ }
1314
+ async function evictStaleLocalSnapshots() {
1315
+ const db = await openDB();
1316
+ return evictStaleSnapshots(db);
1317
+ }
1318
+ async function clearAllLocalSnapshots() {
1319
+ await withStore("readwrite", (store) => store.clear());
1320
+ }
1321
+ var DB_NAME = "vibe-ide-snapshots", DB_VERSION = 1, STORE_NAME = "drafts", MAX_AGE_MS, dbPromise = null;
1322
+ var init_local_snapshot_cache = __esm(() => {
1323
+ MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
1324
+ });
1325
+
1326
+ // src/react/use-autosave.ts
1327
+ import { useCallback, useEffect, useRef, useState } from "react";
1328
+ function useAutosave({
1329
+ draftId,
1330
+ instance,
1331
+ coalescer,
1332
+ enabled = true,
1333
+ persistence,
1334
+ saveMeta
1335
+ }) {
1336
+ const [isDirty, setIsDirty] = useState(false);
1337
+ const [isSaving, setIsSaving] = useState(false);
1338
+ const [lastSavedAt, setLastSavedAt] = useState(null);
1339
+ const [lastError, setLastError] = useState(null);
1340
+ const [remoteSaveStatus, setRemoteSaveStatus] = useState("idle");
1341
+ const [revisionId, setRevisionId] = useState(null);
1342
+ const saveTimerRef = useRef(null);
1343
+ const remoteSaveTimerRef = useRef(null);
1344
+ const isSavingRef = useRef(false);
1345
+ const isRemoteSavingRef = useRef(false);
1346
+ const pendingSaveRef = useRef(false);
1347
+ const pendingRemoteSaveRef = useRef(false);
1348
+ const isDirtyRef = useRef(false);
1349
+ const revisionIdRef = useRef(null);
1350
+ const saveMetaRef = useRef(saveMeta);
1351
+ useEffect(() => {
1352
+ isDirtyRef.current = isDirty;
1353
+ }, [isDirty]);
1354
+ useEffect(() => {
1355
+ saveMetaRef.current = saveMeta;
1356
+ }, [saveMeta]);
1357
+ const doLocalSave = useCallback(async () => {
1358
+ if (!instance || !draftId || isSavingRef.current) {
1359
+ if (isSavingRef.current) {
1360
+ pendingSaveRef.current = true;
1361
+ }
1362
+ return;
1363
+ }
1364
+ isSavingRef.current = true;
1365
+ setIsSaving(true);
1366
+ setLastError(null);
1367
+ try {
1368
+ const tree = await readFileTree(instance);
1369
+ await saveLocalSnapshot(draftId, tree, {
1370
+ savedAt: Date.now(),
1371
+ revisionId: revisionIdRef.current ?? undefined,
1372
+ ...saveMetaRef.current
1373
+ });
1374
+ setIsDirty(false);
1375
+ isDirtyRef.current = false;
1376
+ setLastSavedAt(Date.now());
1377
+ } catch (e) {
1378
+ const error = e instanceof Error ? e : new Error(String(e));
1379
+ setLastError(error);
1380
+ console.error("useAutosave: local save failed", error);
1381
+ } finally {
1382
+ isSavingRef.current = false;
1383
+ setIsSaving(false);
1384
+ if (pendingSaveRef.current) {
1385
+ pendingSaveRef.current = false;
1386
+ window.setTimeout(doLocalSave, 200);
1387
+ }
1388
+ }
1389
+ }, [instance, draftId]);
1390
+ const doRemoteSave = useCallback(async () => {
1391
+ if (!instance || !draftId || !persistence?.saveDraft || isRemoteSavingRef.current) {
1392
+ if (isRemoteSavingRef.current) {
1393
+ pendingRemoteSaveRef.current = true;
1394
+ }
1395
+ return;
1396
+ }
1397
+ isRemoteSavingRef.current = true;
1398
+ setRemoteSaveStatus("saving");
1399
+ try {
1400
+ const tree = await readFileTree(instance);
1401
+ const result = await persistence.saveDraft(draftId, {
1402
+ tree,
1403
+ meta: {
1404
+ revisionId: revisionIdRef.current ?? undefined,
1405
+ savedAt: Date.now(),
1406
+ ...saveMetaRef.current
1407
+ }
1408
+ });
1409
+ if (result.revisionId) {
1410
+ revisionIdRef.current = result.revisionId;
1411
+ setRevisionId(result.revisionId);
1412
+ }
1413
+ setRemoteSaveStatus("saved");
1414
+ } catch (e) {
1415
+ console.error("useAutosave: remote save failed", e);
1416
+ setRemoteSaveStatus("error");
1417
+ } finally {
1418
+ isRemoteSavingRef.current = false;
1419
+ if (pendingRemoteSaveRef.current) {
1420
+ pendingRemoteSaveRef.current = false;
1421
+ window.setTimeout(doRemoteSave, 500);
1422
+ }
1423
+ }
1424
+ }, [instance, draftId, persistence]);
1425
+ const doSave = useCallback(async () => {
1426
+ await doLocalSave();
1427
+ if (persistence?.saveDraft) {
1428
+ doRemoteSave().catch(() => {});
1429
+ }
1430
+ }, [doLocalSave, doRemoteSave, persistence]);
1431
+ const scheduleSave = useCallback(() => {
1432
+ if (saveTimerRef.current !== null) {
1433
+ window.clearTimeout(saveTimerRef.current);
1434
+ }
1435
+ saveTimerRef.current = window.setTimeout(() => {
1436
+ saveTimerRef.current = null;
1437
+ doLocalSave();
1438
+ }, LOCAL_SAVE_IDLE_MS);
1439
+ if (persistence?.saveDraft) {
1440
+ if (remoteSaveTimerRef.current !== null) {
1441
+ window.clearTimeout(remoteSaveTimerRef.current);
1442
+ }
1443
+ remoteSaveTimerRef.current = window.setTimeout(() => {
1444
+ remoteSaveTimerRef.current = null;
1445
+ doRemoteSave();
1446
+ }, REMOTE_SAVE_IDLE_MS);
1447
+ }
1448
+ }, [doLocalSave, doRemoteSave, persistence]);
1449
+ useEffect(() => {
1450
+ if (!coalescer || !enabled)
1451
+ return;
1452
+ const unsub = coalescer.onWrite(() => {
1453
+ setIsDirty(true);
1454
+ isDirtyRef.current = true;
1455
+ scheduleSave();
1456
+ });
1457
+ return () => {
1458
+ unsub();
1459
+ if (saveTimerRef.current !== null) {
1460
+ window.clearTimeout(saveTimerRef.current);
1461
+ saveTimerRef.current = null;
1462
+ }
1463
+ if (remoteSaveTimerRef.current !== null) {
1464
+ window.clearTimeout(remoteSaveTimerRef.current);
1465
+ remoteSaveTimerRef.current = null;
1466
+ }
1467
+ };
1468
+ }, [coalescer, enabled, scheduleSave]);
1469
+ useEffect(() => {
1470
+ if (!isDirty)
1471
+ return;
1472
+ const handler = (e) => {
1473
+ e.preventDefault();
1474
+ e.returnValue = "";
1475
+ };
1476
+ window.addEventListener("beforeunload", handler);
1477
+ return () => window.removeEventListener("beforeunload", handler);
1478
+ }, [isDirty]);
1479
+ useEffect(() => {
1480
+ if (!enabled)
1481
+ return;
1482
+ const handleVisibilityChange = () => {
1483
+ if (document.visibilityState === "hidden" && isDirtyRef.current) {
1484
+ if (saveTimerRef.current !== null) {
1485
+ window.clearTimeout(saveTimerRef.current);
1486
+ saveTimerRef.current = null;
1487
+ }
1488
+ if (remoteSaveTimerRef.current !== null) {
1489
+ window.clearTimeout(remoteSaveTimerRef.current);
1490
+ remoteSaveTimerRef.current = null;
1491
+ }
1492
+ doSave();
1493
+ }
1494
+ };
1495
+ const handlePageHide = () => {
1496
+ if (isDirtyRef.current) {
1497
+ doSave();
1498
+ }
1499
+ };
1500
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1501
+ window.addEventListener("pagehide", handlePageHide);
1502
+ return () => {
1503
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1504
+ window.removeEventListener("pagehide", handlePageHide);
1505
+ };
1506
+ }, [enabled, doSave]);
1507
+ const saveNow = useCallback(async () => {
1508
+ if (saveTimerRef.current !== null) {
1509
+ window.clearTimeout(saveTimerRef.current);
1510
+ saveTimerRef.current = null;
1511
+ }
1512
+ if (remoteSaveTimerRef.current !== null) {
1513
+ window.clearTimeout(remoteSaveTimerRef.current);
1514
+ remoteSaveTimerRef.current = null;
1515
+ }
1516
+ await doSave();
1517
+ }, [doSave]);
1518
+ useEffect(() => {
1519
+ if (!persistence?.loadDraft || !draftId)
1520
+ return;
1521
+ let cancelled = false;
1522
+ (async () => {
1523
+ try {
1524
+ const loaded = await persistence.loadDraft(draftId);
1525
+ if (!cancelled && loaded?.meta.revisionId) {
1526
+ revisionIdRef.current = loaded.meta.revisionId;
1527
+ setRevisionId(loaded.meta.revisionId);
1528
+ }
1529
+ } catch {}
1530
+ })();
1531
+ return () => {
1532
+ cancelled = true;
1533
+ };
1534
+ }, [draftId]);
1535
+ return {
1536
+ isDirty,
1537
+ isSaving,
1538
+ lastSavedAt,
1539
+ lastError,
1540
+ saveNow,
1541
+ remoteSaveStatus,
1542
+ revisionId
1543
+ };
1544
+ }
1545
+ var LOCAL_SAVE_IDLE_MS = 2000, REMOTE_SAVE_IDLE_MS = 15000;
1546
+ var init_use_autosave = __esm(() => {
1547
+ init_filesystem();
1548
+ init_local_snapshot_cache();
1549
+ });
1550
+
1551
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/constants.js
1552
+ var DEFAULT_EDITOR_ORIGIN = "https://stackblitz.com", STORAGE_TOKENS_NAME = "__wc_api_tokens__";
1553
+
1554
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/TypedEventTarget.js
1555
+ class TypedEventTarget {
1556
+ _bus = new EventTarget;
1557
+ listen(listener) {
1558
+ function wrappedListener(event) {
1559
+ listener(event.data);
1560
+ }
1561
+ this._bus.addEventListener("message", wrappedListener);
1562
+ return () => this._bus.removeEventListener("message", wrappedListener);
1563
+ }
1564
+ fireEvent(data) {
1565
+ this._bus.dispatchEvent(new MessageEvent("message", { data }));
1566
+ }
1567
+ }
1568
+
1569
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/tokens.js
1570
+ class Tokens {
1571
+ origin;
1572
+ refresh;
1573
+ access;
1574
+ expires;
1575
+ _revoked = new AbortController;
1576
+ constructor(origin, refresh, access, expires) {
1577
+ this.origin = origin;
1578
+ this.refresh = refresh;
1579
+ this.access = access;
1580
+ this.expires = expires;
1581
+ }
1582
+ async activate(onFailedRefresh) {
1583
+ if (this._revoked.signal.aborted) {
1584
+ throw new Error("Token revoked");
1585
+ }
1586
+ if (this.expires < Date.now()) {
1587
+ if (!await this._fetchNewAccessToken()) {
1588
+ return false;
1589
+ }
1590
+ }
1591
+ this._sync();
1592
+ this._startRefreshTokensLoop(onFailedRefresh);
1593
+ return true;
1594
+ }
1595
+ async revoke(clientId, ignoreRevokeError) {
1596
+ this._revoked.abort();
1597
+ try {
1598
+ const response = await fetch(`${this.origin}/oauth/revoke`, {
1599
+ method: "POST",
1600
+ headers: {
1601
+ "Content-Type": "application/x-www-form-urlencoded"
1602
+ },
1603
+ body: new URLSearchParams({ token: this.refresh, token_type_hint: "refresh_token", client_id: clientId }),
1604
+ mode: "cors"
1605
+ });
1606
+ if (!response.ok) {
1607
+ throw new Error(`Failed to logout`);
1608
+ }
1609
+ } catch (error) {
1610
+ if (!ignoreRevokeError) {
1611
+ throw error;
1612
+ }
1613
+ }
1614
+ clearTokensInStorage();
1615
+ }
1616
+ static fromStorage() {
1617
+ const savedTokens = readTokensFromStorage();
1618
+ if (!savedTokens) {
1619
+ return null;
1620
+ }
1621
+ return new Tokens(savedTokens.origin, savedTokens.refresh, savedTokens.access, savedTokens.expires);
1622
+ }
1623
+ static async fromAuthCode({ editorOrigin, clientId, codeVerifier, authCode, redirectUri }) {
1624
+ const response = await fetch(`${editorOrigin}/oauth/token`, {
1625
+ method: "POST",
1626
+ headers: {
1627
+ "Content-Type": "application/x-www-form-urlencoded"
1628
+ },
1629
+ body: new URLSearchParams({
1630
+ client_id: clientId,
1631
+ code: authCode,
1632
+ code_verifier: codeVerifier,
1633
+ grant_type: "authorization_code",
1634
+ redirect_uri: redirectUri
1635
+ }),
1636
+ mode: "cors"
1637
+ });
1638
+ if (!response.ok) {
1639
+ throw new Error(`Failed to fetch token: ${response.status}`);
1640
+ }
1641
+ const tokenResponse = await response.json();
1642
+ assertTokenResponse(tokenResponse);
1643
+ const { access_token: access, refresh_token: refresh } = tokenResponse;
1644
+ const expires = getExpiresFromTokenResponse(tokenResponse);
1645
+ return new Tokens(editorOrigin, refresh, access, expires);
1646
+ }
1647
+ async _fetchNewAccessToken() {
1648
+ try {
1649
+ const response = await fetch(`${this.origin}/oauth/token`, {
1650
+ method: "POST",
1651
+ headers: {
1652
+ "Content-Type": "application/x-www-form-urlencoded"
1653
+ },
1654
+ body: new URLSearchParams({
1655
+ grant_type: "refresh_token",
1656
+ refresh_token: this.refresh
1657
+ }),
1658
+ mode: "cors",
1659
+ signal: this._revoked.signal
1660
+ });
1661
+ if (!response.ok) {
1662
+ throw IGNORED_ERROR;
1663
+ }
1664
+ const tokenResponse = await response.json();
1665
+ assertTokenResponse(tokenResponse);
1666
+ const { access_token: access, refresh_token: refresh } = tokenResponse;
1667
+ const expires = getExpiresFromTokenResponse(tokenResponse);
1668
+ this.access = access;
1669
+ this.expires = expires;
1670
+ this.refresh = refresh;
1671
+ return true;
1672
+ } catch {
1673
+ clearTokensInStorage();
1674
+ return false;
1675
+ }
1676
+ }
1677
+ _sync() {
1678
+ persistTokensInStorage(this);
1679
+ fireAccessTokenChanged(this.access);
1680
+ }
1681
+ async _startRefreshTokensLoop(onFailedRefresh) {
1682
+ while (true) {
1683
+ const expiresIn = this.expires - Date.now() - 1000;
1684
+ await wait(Math.max(expiresIn, 1000));
1685
+ if (this._revoked.signal.aborted) {
1686
+ return;
1687
+ }
1688
+ if (!this._fetchNewAccessToken()) {
1689
+ onFailedRefresh();
1690
+ return;
1691
+ }
1692
+ this._sync();
1693
+ }
1694
+ }
1695
+ }
1696
+ function clearTokensInStorage() {
1697
+ localStorage.removeItem(STORAGE_TOKENS_NAME);
1698
+ }
1699
+ function addAccessTokenChangedListener(listener) {
1700
+ return accessTokenChangedListeners.listen(listener);
1701
+ }
1702
+ function readTokensFromStorage() {
1703
+ const serializedTokens = localStorage.getItem(STORAGE_TOKENS_NAME);
1704
+ if (!serializedTokens) {
1705
+ return null;
1706
+ }
1707
+ try {
1708
+ return JSON.parse(serializedTokens);
1709
+ } catch {
1710
+ return null;
1711
+ }
1712
+ }
1713
+ function persistTokensInStorage(tokens) {
1714
+ localStorage.setItem(STORAGE_TOKENS_NAME, JSON.stringify(tokens));
1715
+ }
1716
+ function getExpiresFromTokenResponse({ created_at, expires_in }) {
1717
+ return (created_at + expires_in) * 1000;
1718
+ }
1719
+ function assertTokenResponse(token) {
1720
+ if (typeof token !== "object" || !token) {
1721
+ throw new Error("Invalid Token Response");
1722
+ }
1723
+ if (typeof token.access_token !== "string" || typeof token.refresh_token !== "string" || typeof token.created_at !== "number" || typeof token.expires_in !== "number") {
1724
+ throw new Error("Invalid Token Response");
1725
+ }
1726
+ }
1727
+ function wait(ms) {
1728
+ return new Promise((resolve) => setTimeout(resolve, ms));
1729
+ }
1730
+ function fireAccessTokenChanged(accessToken) {
1731
+ accessTokenChangedListeners.fireEvent(accessToken);
1732
+ }
1733
+ var IGNORED_ERROR, accessTokenChangedListeners;
1734
+ var init_tokens = __esm(() => {
1735
+ IGNORED_ERROR = new Error;
1736
+ IGNORED_ERROR.stack = "";
1737
+ accessTokenChangedListeners = new TypedEventTarget;
1738
+ });
1739
+
1740
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/iframe-url.js
1741
+ var params, editorOrigin = null, iframeSettings;
1742
+ var init_iframe_url = __esm(() => {
1743
+ params = {};
1744
+ iframeSettings = {
1745
+ get editorOrigin() {
1746
+ if (editorOrigin == null) {
1747
+ editorOrigin = new URL(globalThis.WEBCONTAINER_API_IFRAME_URL ?? DEFAULT_EDITOR_ORIGIN).origin;
1748
+ }
1749
+ return editorOrigin;
1750
+ },
1751
+ set editorOrigin(newOrigin) {
1752
+ editorOrigin = new URL(newOrigin).origin;
1753
+ },
1754
+ setQueryParam(key, value) {
1755
+ params[key] = value;
1756
+ },
1757
+ get url() {
1758
+ const url = new URL(this.editorOrigin);
1759
+ url.pathname = "/headless";
1760
+ for (const param in params) {
1761
+ url.searchParams.set(param, params[param]);
1762
+ }
1763
+ url.searchParams.set("version", "1.6.1");
1764
+ return url;
1765
+ }
1766
+ };
1767
+ });
1768
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/reset-promise.js
1769
+ function resettablePromise() {
1770
+ let resolve;
1771
+ let promise;
1772
+ function reset() {
1773
+ promise = new Promise((_resolve) => resolve = _resolve);
1774
+ }
1775
+ reset();
1776
+ return {
1777
+ get promise() {
1778
+ return promise;
1779
+ },
1780
+ resolve(value) {
1781
+ return resolve(value);
1782
+ },
1783
+ reset
1784
+ };
1785
+ }
1786
+
1787
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/internal/auth-state.js
1788
+ function assertAuthTokens(tokens) {
1789
+ if (!tokens) {
1790
+ throw new Error("Oops! Tokens is not defined when it always should be.");
1791
+ }
1792
+ }
1793
+ var authState, authFailedListeners, loggedOutListeners;
1794
+ var init_auth_state = __esm(() => {
1795
+ init_tokens();
1796
+ init_iframe_url();
1797
+ authState = {
1798
+ initialized: false,
1799
+ bootCalled: false,
1800
+ authComplete: resettablePromise(),
1801
+ clientId: "",
1802
+ oauthScope: "",
1803
+ broadcastChannel: null,
1804
+ get editorOrigin() {
1805
+ return iframeSettings.editorOrigin;
1806
+ },
1807
+ tokens: null
1808
+ };
1809
+ authFailedListeners = new TypedEventTarget;
1810
+ loggedOutListeners = new TypedEventTarget;
1811
+ });
1812
+
1813
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/preview-message-types.js
1814
+ var PreviewMessageType;
1815
+ var init_preview_message_types = __esm(() => {
1816
+ (function(PreviewMessageType2) {
1817
+ PreviewMessageType2["UncaughtException"] = "PREVIEW_UNCAUGHT_EXCEPTION";
1818
+ PreviewMessageType2["UnhandledRejection"] = "PREVIEW_UNHANDLED_REJECTION";
1819
+ PreviewMessageType2["ConsoleError"] = "PREVIEW_CONSOLE_ERROR";
1820
+ })(PreviewMessageType || (PreviewMessageType = {}));
1821
+ });
1822
+
1823
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/vendor/index.js
1824
+ function expose(obj, ep = self) {
1825
+ ep.addEventListener("message", function callback(ev) {
1826
+ if (!ev || !ev.data) {
1827
+ return;
1828
+ }
1829
+ const { id, type, path } = Object.assign({ path: [] }, ev.data);
1830
+ const argumentList = (ev.data.argumentList || []).map(fromWireValue);
1831
+ let returnValue;
1832
+ try {
1833
+ const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);
1834
+ const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);
1835
+ switch (type) {
1836
+ case 0:
1837
+ {
1838
+ returnValue = rawValue;
1839
+ }
1840
+ break;
1841
+ case 1:
1842
+ {
1843
+ parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
1844
+ returnValue = true;
1845
+ }
1846
+ break;
1847
+ case 2:
1848
+ {
1849
+ returnValue = rawValue.apply(parent, argumentList);
1850
+ }
1851
+ break;
1852
+ case 3:
1853
+ {
1854
+ const value = new rawValue(...argumentList);
1855
+ returnValue = proxy(value);
1856
+ }
1857
+ break;
1858
+ case 4:
1859
+ {
1860
+ const { port1, port2 } = new MessageChannel;
1861
+ expose(obj, port2);
1862
+ returnValue = transfer(port1, [port1]);
1863
+ }
1864
+ break;
1865
+ case 5:
1866
+ {
1867
+ returnValue = undefined;
1868
+ }
1869
+ break;
1870
+ }
1871
+ } catch (value) {
1872
+ returnValue = { value, [throwMarker]: 0 };
1873
+ }
1874
+ Promise.resolve(returnValue).catch((value) => {
1875
+ return { value, [throwMarker]: 0 };
1876
+ }).then((returnValue2) => {
1877
+ const [wireValue, transferables] = toWireValue(returnValue2);
1878
+ ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
1879
+ if (type === 5) {
1880
+ ep.removeEventListener("message", callback);
1881
+ closeEndPoint(ep);
1882
+ }
1883
+ });
1884
+ });
1885
+ if (ep.start) {
1886
+ ep.start();
1887
+ }
1888
+ }
1889
+ function isMessagePort(endpoint) {
1890
+ return endpoint.constructor.name === "MessagePort";
1891
+ }
1892
+ function closeEndPoint(endpoint) {
1893
+ if (isMessagePort(endpoint))
1894
+ endpoint.close();
1895
+ }
1896
+ function wrap(ep, target) {
1897
+ return createProxy(ep, [], target);
1898
+ }
1899
+ function throwIfProxyReleased(isReleased) {
1900
+ if (isReleased) {
1901
+ throw new Error("Proxy has been released and is not useable");
1902
+ }
1903
+ }
1904
+ function createProxy(ep, path = [], target = function() {}) {
1905
+ let isProxyReleased = false;
1906
+ const proxy2 = new Proxy(target, {
1907
+ get(_target, prop) {
1908
+ throwIfProxyReleased(isProxyReleased);
1909
+ if (prop === releaseProxy) {
1910
+ return () => {
1911
+ return requestResponseMessage(ep, {
1912
+ type: 5,
1913
+ path: path.map((p) => p.toString())
1914
+ }).then(() => {
1915
+ closeEndPoint(ep);
1916
+ isProxyReleased = true;
1917
+ });
1918
+ };
1919
+ }
1920
+ if (prop === "then") {
1921
+ if (path.length === 0) {
1922
+ return { then: () => proxy2 };
1923
+ }
1924
+ const r = requestResponseMessage(ep, {
1925
+ type: 0,
1926
+ path: path.map((p) => p.toString())
1927
+ }).then(fromWireValue);
1928
+ return r.then.bind(r);
1929
+ }
1930
+ return createProxy(ep, [...path, prop]);
1931
+ },
1932
+ set(_target, prop, rawValue) {
1933
+ throwIfProxyReleased(isProxyReleased);
1934
+ const [value, transferables] = toWireValue(rawValue);
1935
+ return requestResponseMessage(ep, {
1936
+ type: 1,
1937
+ path: [...path, prop].map((p) => p.toString()),
1938
+ value
1939
+ }, transferables).then(fromWireValue);
1940
+ },
1941
+ apply(_target, _thisArg, rawArgumentList) {
1942
+ throwIfProxyReleased(isProxyReleased);
1943
+ const last = path[path.length - 1];
1944
+ if (last === createEndpoint) {
1945
+ return requestResponseMessage(ep, {
1946
+ type: 4
1947
+ }).then(fromWireValue);
1948
+ }
1949
+ if (last === "bind") {
1950
+ return createProxy(ep, path.slice(0, -1));
1951
+ }
1952
+ const [argumentList, transferables] = processArguments(rawArgumentList);
1953
+ return requestResponseMessage(ep, {
1954
+ type: 2,
1955
+ path: path.map((p) => p.toString()),
1956
+ argumentList
1957
+ }, transferables).then(fromWireValue);
1958
+ },
1959
+ construct(_target, rawArgumentList) {
1960
+ throwIfProxyReleased(isProxyReleased);
1961
+ const [argumentList, transferables] = processArguments(rawArgumentList);
1962
+ return requestResponseMessage(ep, {
1963
+ type: 3,
1964
+ path: path.map((p) => p.toString()),
1965
+ argumentList
1966
+ }, transferables).then(fromWireValue);
1967
+ }
1968
+ });
1969
+ return proxy2;
1970
+ }
1971
+ function myFlat(arr) {
1972
+ return Array.prototype.concat.apply([], arr);
1973
+ }
1974
+ function processArguments(argumentList) {
1975
+ const processed = argumentList.map(toWireValue);
1976
+ return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
1977
+ }
1978
+ function transfer(obj, transfers) {
1979
+ transferCache.set(obj, transfers);
1980
+ return obj;
1981
+ }
1982
+ function proxy(obj) {
1983
+ return Object.assign(obj, { [proxyMarker]: true });
1984
+ }
1985
+ function windowEndpoint(w, context = self, targetOrigin = "*") {
1986
+ return {
1987
+ postMessage: (msg, transferables) => w.postMessage(msg, targetOrigin, transferables),
1988
+ addEventListener: context.addEventListener.bind(context),
1989
+ removeEventListener: context.removeEventListener.bind(context)
1990
+ };
1991
+ }
1992
+ function toWireValue(value) {
1993
+ for (const [name, handler] of transferHandlers) {
1994
+ if (handler.canHandle(value)) {
1995
+ const [serializedValue, transferables] = handler.serialize(value);
1996
+ return [
1997
+ {
1998
+ type: 3,
1999
+ name,
2000
+ value: serializedValue
2001
+ },
2002
+ transferables
2003
+ ];
2004
+ }
2005
+ }
2006
+ return [
2007
+ {
2008
+ type: 0,
2009
+ value
2010
+ },
2011
+ transferCache.get(value) || []
2012
+ ];
2013
+ }
2014
+ function fromWireValue(value) {
2015
+ switch (value.type) {
2016
+ case 3:
2017
+ return transferHandlers.get(value.name).deserialize(value.value);
2018
+ case 0:
2019
+ return value.value;
2020
+ }
2021
+ }
2022
+ function requestResponseMessage(ep, msg, transfers) {
2023
+ return new Promise((resolve) => {
2024
+ const id = generateUUID();
2025
+ ep.addEventListener("message", function l(ev) {
2026
+ if (!ev.data || !ev.data.id || ev.data.id !== id) {
2027
+ return;
2028
+ }
2029
+ ep.removeEventListener("message", l);
2030
+ resolve(ev.data);
2031
+ });
2032
+ if (ep.start) {
2033
+ ep.start();
2034
+ }
2035
+ ep.postMessage(Object.assign({ id }, msg), transfers);
2036
+ });
2037
+ }
2038
+ function generateUUID() {
2039
+ return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");
2040
+ }
2041
+ var __defProp2, __export2 = (target, all) => {
2042
+ for (var name in all)
2043
+ __defProp2(target, name, { get: all[name], enumerable: true });
2044
+ }, comlink_exports, proxyMarker, createEndpoint, releaseProxy, throwMarker, isObject = (val) => typeof val === "object" && val !== null || typeof val === "function", proxyTransferHandler, throwTransferHandler, transferHandlers, transferCache;
2045
+ var init_vendor = __esm(() => {
2046
+ __defProp2 = Object.defineProperty;
2047
+ comlink_exports = {};
2048
+ __export2(comlink_exports, {
2049
+ createEndpoint: () => createEndpoint,
2050
+ expose: () => expose,
2051
+ proxy: () => proxy,
2052
+ proxyMarker: () => proxyMarker,
2053
+ releaseProxy: () => releaseProxy,
2054
+ transfer: () => transfer,
2055
+ transferHandlers: () => transferHandlers,
2056
+ windowEndpoint: () => windowEndpoint,
2057
+ wrap: () => wrap
2058
+ });
2059
+ proxyMarker = Symbol("Comlink.proxy");
2060
+ createEndpoint = Symbol("Comlink.endpoint");
2061
+ releaseProxy = Symbol("Comlink.releaseProxy");
2062
+ throwMarker = Symbol("Comlink.thrown");
2063
+ proxyTransferHandler = {
2064
+ canHandle: (val) => isObject(val) && val[proxyMarker],
2065
+ serialize(obj) {
2066
+ const { port1, port2 } = new MessageChannel;
2067
+ expose(obj, port1);
2068
+ return [port2, [port2]];
2069
+ },
2070
+ deserialize(port) {
2071
+ port.start();
2072
+ return wrap(port);
2073
+ }
2074
+ };
2075
+ throwTransferHandler = {
2076
+ canHandle: (value) => isObject(value) && (throwMarker in value),
2077
+ serialize({ value }) {
2078
+ let serialized;
2079
+ if (value instanceof Error) {
2080
+ serialized = {
2081
+ isError: true,
2082
+ value: {
2083
+ message: value.message,
2084
+ name: value.name,
2085
+ stack: value.stack
2086
+ }
2087
+ };
2088
+ } else {
2089
+ serialized = { isError: false, value };
2090
+ }
2091
+ return [serialized, []];
2092
+ },
2093
+ deserialize(serialized) {
2094
+ if (serialized.isError) {
2095
+ throw Object.assign(new Error(serialized.value.message), serialized.value);
2096
+ }
2097
+ throw serialized.value;
2098
+ }
2099
+ };
2100
+ transferHandlers = /* @__PURE__ */ new Map([
2101
+ ["proxy", proxyTransferHandler],
2102
+ ["throw", throwTransferHandler]
2103
+ ]);
2104
+ transferCache = /* @__PURE__ */ new WeakMap;
2105
+ });
2106
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/utils/is-preview-message.js
2107
+ function isPreviewMessage(data) {
2108
+ if (data == null || typeof data !== "object") {
2109
+ return false;
2110
+ }
2111
+ if (!("type" in data) || !PREVIEW_MESSAGE_TYPES.includes(data.type)) {
2112
+ return false;
2113
+ }
2114
+ return true;
2115
+ }
2116
+ var PREVIEW_MESSAGE_TYPES;
2117
+ var init_is_preview_message = __esm(() => {
2118
+ init_preview_message_types();
2119
+ PREVIEW_MESSAGE_TYPES = [
2120
+ PreviewMessageType.ConsoleError,
2121
+ PreviewMessageType.UncaughtException,
2122
+ PreviewMessageType.UnhandledRejection
2123
+ ];
2124
+ });
2125
+
2126
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/utils.js
2127
+ var init_utils = __esm(() => {
2128
+ init_preview_message_types();
2129
+ init_is_preview_message();
2130
+ });
2131
+
2132
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/utils/null-prototype.js
2133
+ function nullPrototype(source) {
2134
+ const prototype = Object.create(null);
2135
+ if (!source) {
2136
+ return prototype;
2137
+ }
2138
+ return Object.assign(prototype, source);
2139
+ }
2140
+
2141
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/utils/file-system.js
2142
+ function toInternalFileSystemTree(tree) {
2143
+ const newTree = { d: {} };
2144
+ for (const name of Object.keys(tree)) {
2145
+ const entry = tree[name];
2146
+ if ("file" in entry) {
2147
+ if ("symlink" in entry.file) {
2148
+ newTree.d[name] = { f: { l: entry.file.symlink } };
2149
+ continue;
2150
+ }
2151
+ const contents = entry.file.contents;
2152
+ const stringContents = typeof contents === "string" ? contents : binaryDecoder.decode(contents);
2153
+ const binary = typeof contents === "string" ? {} : { b: true };
2154
+ newTree.d[name] = { f: { c: stringContents, ...binary } };
2155
+ continue;
2156
+ }
2157
+ const newEntry = toInternalFileSystemTree(entry.directory);
2158
+ newTree.d[name] = newEntry;
2159
+ }
2160
+ return newTree;
2161
+ }
2162
+ function toExternalFileSystemTree(tree) {
2163
+ const newTree = nullPrototype();
2164
+ if ("f" in tree) {
2165
+ throw new Error("It is not possible to export a single file in the JSON format.");
2166
+ }
2167
+ if ("d" in tree) {
2168
+ for (const name of Object.keys(tree.d)) {
2169
+ const entry = tree.d[name];
2170
+ if ("d" in entry) {
2171
+ newTree[name] = nullPrototype({
2172
+ directory: toExternalFileSystemTree(entry)
2173
+ });
2174
+ } else if ("f" in entry) {
2175
+ if ("c" in entry.f) {
2176
+ newTree[name] = nullPrototype({
2177
+ file: nullPrototype({
2178
+ contents: entry.f.b ? fromBinaryString(entry.f.c) : entry.f.c
2179
+ })
2180
+ });
2181
+ } else if ("l" in entry.f) {
2182
+ newTree[name] = nullPrototype({
2183
+ file: nullPrototype({
2184
+ symlink: entry.f.l
2185
+ })
2186
+ });
2187
+ }
2188
+ }
2189
+ }
2190
+ }
2191
+ return newTree;
2192
+ }
2193
+ function fromBinaryString(s) {
2194
+ const encoded = new Uint8Array(s.length);
2195
+ for (let i = 0;i < s.length; i++) {
2196
+ encoded[i] = s[i].charCodeAt(0);
2197
+ }
2198
+ return encoded;
2199
+ }
2200
+ var binaryDecoder;
2201
+ var init_file_system = __esm(() => {
2202
+ binaryDecoder = new TextDecoder("latin1");
2203
+ });
2204
+
2205
+ // ../../node_modules/.bun/@webcontainer+api@1.6.1/node_modules/@webcontainer/api/dist/index.js
2206
+ class WebContainer {
2207
+ _instance;
2208
+ _runtimeInfo;
2209
+ fs;
2210
+ static _instance = null;
2211
+ static _teardownPromise = null;
2212
+ _tornDown = false;
2213
+ _unsubscribeFromTokenChangedListener = () => {};
2214
+ constructor(_instance, fs, previewScript, _runtimeInfo) {
2215
+ this._instance = _instance;
2216
+ this._runtimeInfo = _runtimeInfo;
2217
+ this.fs = new FileSystemAPIClient(fs);
2218
+ if (authState.initialized) {
2219
+ this._unsubscribeFromTokenChangedListener = addAccessTokenChangedListener((accessToken) => {
2220
+ this._instance.setCredentials({ accessToken, editorOrigin: authState.editorOrigin });
2221
+ });
2222
+ (async () => {
2223
+ await authState.authComplete.promise;
2224
+ if (this._tornDown) {
2225
+ return;
2226
+ }
2227
+ assertAuthTokens(authState.tokens);
2228
+ await this._instance.setCredentials({
2229
+ accessToken: authState.tokens.access,
2230
+ editorOrigin: authState.editorOrigin
2231
+ });
2232
+ })().catch((error) => {
2233
+ console.error(error);
2234
+ });
2235
+ }
2236
+ }
2237
+ async spawn(command, optionsOrArgs, options) {
2238
+ let args = [];
2239
+ if (Array.isArray(optionsOrArgs)) {
2240
+ args = optionsOrArgs;
2241
+ } else {
2242
+ options = optionsOrArgs;
2243
+ }
2244
+ let output = undefined;
2245
+ let outputStream = new ReadableStream;
2246
+ if (options?.output !== false) {
2247
+ const result = streamWithPush();
2248
+ output = result.push;
2249
+ outputStream = result.stream;
2250
+ }
2251
+ let stdout = undefined;
2252
+ let stdoutStream;
2253
+ let stderr = undefined;
2254
+ let stderrStream;
2255
+ const wrappedOutput = proxyListener(binaryListener(output));
2256
+ const wrappedStdout = proxyListener(binaryListener(stdout));
2257
+ const wrappedStderr = proxyListener(binaryListener(stderr));
2258
+ const process = await this._instance.run({
2259
+ command,
2260
+ args,
2261
+ cwd: options?.cwd,
2262
+ env: options?.env,
2263
+ terminal: options?.terminal
2264
+ }, wrappedStdout, wrappedStderr, wrappedOutput);
2265
+ return new WebContainerProcessImpl(process, outputStream, stdoutStream, stderrStream);
2266
+ }
2267
+ async export(path, options) {
2268
+ const serializeOptions = {
2269
+ format: options?.format ?? "json",
2270
+ includes: options?.includes,
2271
+ excludes: options?.excludes,
2272
+ external: true
2273
+ };
2274
+ const result = await this._instance.serialize(path, serializeOptions);
2275
+ if (serializeOptions.format === "json") {
2276
+ const data = JSON.parse(decoder.decode(result));
2277
+ return toExternalFileSystemTree(data);
2278
+ }
2279
+ return result;
2280
+ }
2281
+ on(event, listener) {
2282
+ if (event === "preview-message") {
2283
+ const originalListener = listener;
2284
+ listener = (message) => {
2285
+ if (isPreviewMessage(message)) {
2286
+ originalListener(message);
2287
+ }
2288
+ };
2289
+ }
2290
+ const { listener: wrapped, subscribe } = syncSubscription(listener);
2291
+ return subscribe(this._instance.on(event, comlink_exports.proxy(wrapped)));
2292
+ }
2293
+ mount(snapshotOrTree, options) {
2294
+ const payload = snapshotOrTree instanceof Uint8Array ? snapshotOrTree : snapshotOrTree instanceof ArrayBuffer ? new Uint8Array(snapshotOrTree) : encoder.encode(JSON.stringify(toInternalFileSystemTree(snapshotOrTree)));
2295
+ return this._instance.loadFiles(comlink_exports.transfer(payload, [payload.buffer]), {
2296
+ mountPoints: options?.mountPoint
2297
+ });
2298
+ }
2299
+ setPreviewScript(scriptSrc, options) {
2300
+ return this._instance.setPreviewScript(scriptSrc, options);
2301
+ }
2302
+ get path() {
2303
+ return this._runtimeInfo.path;
2304
+ }
2305
+ get workdir() {
2306
+ return this._runtimeInfo.cwd;
2307
+ }
2308
+ teardown() {
2309
+ if (this._tornDown) {
2310
+ throw new Error("WebContainer already torn down");
2311
+ }
2312
+ this._tornDown = true;
2313
+ this._unsubscribeFromTokenChangedListener();
2314
+ const teardownFn = async () => {
2315
+ try {
2316
+ await this.fs._teardown();
2317
+ await this._instance.teardown();
2318
+ } finally {
2319
+ this._instance[comlink_exports.releaseProxy]();
2320
+ if (WebContainer._instance === this) {
2321
+ WebContainer._instance = null;
2322
+ }
2323
+ }
2324
+ };
2325
+ WebContainer._teardownPromise = teardownFn();
2326
+ }
2327
+ static async boot(options = {}) {
2328
+ await this._teardownPromise;
2329
+ WebContainer._teardownPromise = null;
2330
+ const { workdirName } = options;
2331
+ if (window.crossOriginIsolated && options.coep === "none") {
2332
+ console.warn(`A Cross-Origin-Embedder-Policy header is required in cross origin isolated environments.
2333
+ Set the 'coep' option to 'require-corp'.`);
2334
+ }
2335
+ if (workdirName?.includes("/") || workdirName === ".." || workdirName === ".") {
2336
+ throw new Error("workdirName should be a valid folder name");
2337
+ }
2338
+ authState.bootCalled = true;
2339
+ while (bootPromise) {
2340
+ await bootPromise;
2341
+ }
2342
+ if (WebContainer._instance) {
2343
+ throw new Error("Only a single WebContainer instance can be booted");
2344
+ }
2345
+ const instancePromise = unsynchronizedBoot(options);
2346
+ bootPromise = instancePromise.catch(() => {});
2347
+ try {
2348
+ const instance = await instancePromise;
2349
+ WebContainer._instance = instance;
2350
+ return instance;
2351
+ } finally {
2352
+ bootPromise = null;
2353
+ }
2354
+ }
2355
+ }
2356
+
2357
+ class DirEntImpl {
2358
+ name;
2359
+ _type;
2360
+ constructor(name, _type) {
2361
+ this.name = name;
2362
+ this._type = _type;
2363
+ }
2364
+ isFile() {
2365
+ return this._type === DIR_ENTRY_TYPE_FILE;
2366
+ }
2367
+ isDirectory() {
2368
+ return this._type === DIR_ENTRY_TYPE_DIR;
2369
+ }
2370
+ }
2371
+
2372
+ class FSWatcher {
2373
+ _apiClient;
2374
+ _path;
2375
+ _options;
2376
+ _listener;
2377
+ _wrappedListener;
2378
+ _watcher;
2379
+ _closed = false;
2380
+ constructor(_apiClient, _path, _options, _listener) {
2381
+ this._apiClient = _apiClient;
2382
+ this._path = _path;
2383
+ this._options = _options;
2384
+ this._listener = _listener;
2385
+ this._apiClient._watchers.add(this);
2386
+ this._wrappedListener = (event, filename) => {
2387
+ if (this._listener && !this._closed) {
2388
+ this._listener(event, filename);
2389
+ }
2390
+ };
2391
+ this._apiClient._fs.watch(this._path, this._options, proxyListener(this._wrappedListener)).then((_watcher) => {
2392
+ this._watcher = _watcher;
2393
+ if (this._closed) {
2394
+ return this._teardown();
2395
+ }
2396
+ return;
2397
+ }).catch(console.error);
2398
+ }
2399
+ async close() {
2400
+ if (!this._closed) {
2401
+ this._closed = true;
2402
+ this._apiClient._watchers.delete(this);
2403
+ await this._teardown();
2404
+ }
2405
+ }
2406
+ async _teardown() {
2407
+ await this._watcher?.close().finally(() => {
2408
+ this._watcher?.[comlink_exports.releaseProxy]();
2409
+ });
2410
+ }
2411
+ }
2412
+
2413
+ class WebContainerProcessImpl {
2414
+ output;
2415
+ input;
2416
+ exit;
2417
+ _process;
2418
+ stdout;
2419
+ stderr;
2420
+ constructor(process, output, stdout, stderr) {
2421
+ this.output = output;
2422
+ this._process = process;
2423
+ this.input = new WritableStream({
2424
+ write: (data) => {
2425
+ this._getProcess()?.write(data).catch(() => {});
2426
+ }
2427
+ });
2428
+ this.exit = this._onExit();
2429
+ this.stdout = stdout;
2430
+ this.stderr = stderr;
2431
+ }
2432
+ kill() {
2433
+ this._process?.kill();
2434
+ }
2435
+ resize(dimensions) {
2436
+ this._getProcess()?.resize(dimensions);
2437
+ }
2438
+ async _onExit() {
2439
+ try {
2440
+ return await this._process.onExit;
2441
+ } finally {
2442
+ this._process?.[comlink_exports.releaseProxy]();
2443
+ this._process = null;
2444
+ }
2445
+ }
2446
+ _getProcess() {
2447
+ if (this._process == null) {
2448
+ console.warn("This process already exited");
2449
+ }
2450
+ return this._process;
2451
+ }
2452
+ }
2453
+
2454
+ class FileSystemAPIClient {
2455
+ _fs;
2456
+ _watchers = new Set([]);
2457
+ constructor(fs) {
2458
+ this._fs = fs;
2459
+ }
2460
+ rm(...args) {
2461
+ return this._fs.rm(...args);
2462
+ }
2463
+ async readFile(path, encoding) {
2464
+ return await this._fs.readFile(path, encoding);
2465
+ }
2466
+ async rename(oldPath, newPath) {
2467
+ return await this._fs.rename(oldPath, newPath);
2468
+ }
2469
+ async writeFile(path, data, options) {
2470
+ if (data instanceof Uint8Array) {
2471
+ const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
2472
+ data = comlink_exports.transfer(new Uint8Array(buffer), [buffer]);
2473
+ }
2474
+ await this._fs.writeFile(path, data, options);
2475
+ }
2476
+ async readdir(path, options) {
2477
+ const result = await this._fs.readdir(path, options);
2478
+ if (isStringArray(result)) {
2479
+ return result;
2480
+ }
2481
+ if (isTypedArrayCollection(result)) {
2482
+ return result;
2483
+ }
2484
+ const entries = result.map((entry) => new DirEntImpl(entry.name, entry["Symbol(type)"]));
2485
+ return entries;
2486
+ }
2487
+ async mkdir(path, options) {
2488
+ return await this._fs.mkdir(path, options);
2489
+ }
2490
+ watch(path, options, listener) {
2491
+ if (typeof options === "function") {
2492
+ listener = options;
2493
+ options = null;
2494
+ }
2495
+ return new FSWatcher(this, path, options, listener);
2496
+ }
2497
+ async _teardown() {
2498
+ this._fs[comlink_exports.releaseProxy]();
2499
+ await Promise.all([...this._watchers].map((watcher) => watcher.close()));
2500
+ }
2501
+ }
2502
+ async function unsynchronizedBoot(options) {
2503
+ const { serverPromise } = serverFactory(options);
2504
+ const server = await serverPromise;
2505
+ const instance = await server.build({
2506
+ host: window.location.host,
2507
+ version: "1.6.1",
2508
+ workdirName: options.workdirName,
2509
+ forwardPreviewErrors: options.forwardPreviewErrors
2510
+ });
2511
+ const [fs, previewScript, runtimeInfo] = await Promise.all([
2512
+ instance.fs(),
2513
+ instance.previewScript(),
2514
+ instance.runtimeInfo()
2515
+ ]);
2516
+ return new WebContainer(instance, fs, previewScript, runtimeInfo);
2517
+ }
2518
+ function binaryListener(listener) {
2519
+ if (listener == null) {
2520
+ return;
2521
+ }
2522
+ return (data) => {
2523
+ if (data instanceof Uint8Array) {
2524
+ listener(decoder.decode(data));
2525
+ } else if (data == null) {
2526
+ listener(null);
2527
+ }
2528
+ };
2529
+ }
2530
+ function proxyListener(listener) {
2531
+ if (listener == null) {
2532
+ return;
2533
+ }
2534
+ return comlink_exports.proxy(listener);
2535
+ }
2536
+ function serverFactory(options) {
2537
+ if (cachedServerPromise != null) {
2538
+ if (options.coep !== cachedBootOptions.coep) {
2539
+ console.warn(`Attempting to boot WebContainer with 'coep: ${options.coep}'`);
2540
+ console.warn(`First boot had 'coep: ${cachedBootOptions.coep}', new settings will not take effect!`);
2541
+ }
2542
+ return { serverPromise: cachedServerPromise };
2543
+ }
2544
+ if (options.coep) {
2545
+ iframeSettings.setQueryParam("coep", options.coep);
2546
+ }
2547
+ if (options.experimentalNode) {
2548
+ iframeSettings.setQueryParam("experimental_node", "1");
2549
+ }
2550
+ const iframe = document.createElement("iframe");
2551
+ iframe.style.display = "none";
2552
+ iframe.setAttribute("allow", "cross-origin-isolated");
2553
+ const url = iframeSettings.url;
2554
+ iframe.src = url.toString();
2555
+ const { origin } = url;
2556
+ cachedBootOptions = { ...options };
2557
+ cachedServerPromise = new Promise((resolve) => {
2558
+ const onMessage = (event) => {
2559
+ if (event.origin !== origin) {
2560
+ return;
2561
+ }
2562
+ const { data } = event;
2563
+ if (data.type === "init") {
2564
+ resolve(comlink_exports.wrap(event.ports[0]));
2565
+ return;
2566
+ }
2567
+ if (data.type === "warning") {
2568
+ console[data.level].call(console, data.message);
2569
+ return;
2570
+ }
2571
+ };
2572
+ window.addEventListener("message", onMessage);
2573
+ });
2574
+ document.body.insertBefore(iframe, null);
2575
+ return { serverPromise: cachedServerPromise };
2576
+ }
2577
+ function isStringArray(list) {
2578
+ return typeof list[0] === "string";
2579
+ }
2580
+ function isTypedArrayCollection(list) {
2581
+ return list[0] instanceof Uint8Array;
2582
+ }
2583
+ function streamWithPush() {
2584
+ let controller = null;
2585
+ const stream = new ReadableStream({
2586
+ start(controller_) {
2587
+ controller = controller_;
2588
+ }
2589
+ });
2590
+ const push = (item) => {
2591
+ if (item != null) {
2592
+ controller?.enqueue(item);
2593
+ } else {
2594
+ controller?.close();
2595
+ controller = null;
2596
+ }
2597
+ };
2598
+ return { stream, push };
2599
+ }
2600
+ function syncSubscription(listener) {
2601
+ let stopped = false;
2602
+ let unsubscribe = () => {};
2603
+ const wrapped = (...args) => {
2604
+ if (stopped) {
2605
+ return;
2606
+ }
2607
+ listener(...args);
2608
+ };
2609
+ return {
2610
+ subscribe(promise) {
2611
+ promise.then((unsubscribe_) => {
2612
+ unsubscribe = unsubscribe_;
2613
+ if (stopped) {
2614
+ unsubscribe();
2615
+ }
2616
+ });
2617
+ return () => {
2618
+ stopped = true;
2619
+ unsubscribe();
2620
+ };
2621
+ },
2622
+ listener: wrapped
2623
+ };
2624
+ }
2625
+ var bootPromise = null, cachedServerPromise = null, cachedBootOptions, decoder, encoder, DIR_ENTRY_TYPE_FILE = 1, DIR_ENTRY_TYPE_DIR = 2;
2626
+ var init_dist = __esm(() => {
2627
+ init_auth_state();
2628
+ init_preview_message_types();
2629
+ init_vendor();
2630
+ init_auth_state();
2631
+ init_tokens();
2632
+ init_iframe_url();
2633
+ init_utils();
2634
+ init_file_system();
2635
+ init_utils();
2636
+ cachedBootOptions = {};
2637
+ decoder = new TextDecoder;
2638
+ encoder = new TextEncoder;
2639
+ });
2640
+
2641
+ // src/core/boot.ts
2642
+ var exports_boot = {};
2643
+ __export(exports_boot, {
2644
+ teardownWebContainer: () => teardownWebContainer,
2645
+ getWebContainer: () => getWebContainer,
2646
+ bootWebContainer: () => bootWebContainer,
2647
+ WebContainerTabBlockedError: () => WebContainerTabBlockedError,
2648
+ WebContainerBootTimeoutError: () => WebContainerBootTimeoutError
2649
+ });
2650
+ function isCoordinationMessage(data) {
2651
+ return typeof data === "object" && data !== null && "type" in data && data.type !== undefined && ["ping", "pong", "takeover"].includes(String(data.type));
2652
+ }
2653
+ function createCoordinationMessage(type, token) {
2654
+ return token ? { type, token } : type;
2655
+ }
2656
+ function bootWithCredentiallessIframe() {
2657
+ const originalCreateElement = document.createElement.bind(document);
2658
+ document.createElement = (tagName, options) => {
2659
+ const element = options === undefined ? originalCreateElement(tagName) : originalCreateElement(tagName, options);
2660
+ if (tagName.toLowerCase() === "iframe") {
2661
+ const iframe = element;
2662
+ iframe.setAttribute("credentialless", "");
2663
+ if ("credentialless" in iframe) {
2664
+ iframe.credentialless = true;
2665
+ }
2666
+ }
2667
+ return element;
2668
+ };
2669
+ try {
2670
+ return WebContainer.boot({
2671
+ coep: "credentialless",
2672
+ forwardPreviewErrors: "exceptions-only"
2673
+ });
2674
+ } finally {
2675
+ document.createElement = originalCreateElement;
2676
+ }
2677
+ }
2678
+ async function bootWebContainer(options) {
2679
+ if (typeof window === "undefined") {
2680
+ throw new Error("WebContainer can only be initialized in the browser.");
2681
+ }
2682
+ if (!window.crossOriginIsolated) {
2683
+ throw new Error("WebContainer requires cross-origin isolation.");
2684
+ }
2685
+ if (bootPromise2)
2686
+ return bootPromise2;
2687
+ const channelName = options?.channelName ?? DEFAULT_CHANNEL_NAME;
2688
+ const channelToken = options?.channelToken;
2689
+ bootPromise2 = new Promise(async (resolve, reject) => {
2690
+ let settled = false;
2691
+ const fail = (error) => {
2692
+ if (settled)
2693
+ return;
2694
+ settled = true;
2695
+ bootPromise2 = null;
2696
+ if (channel) {
2697
+ channel.close();
2698
+ channel = null;
2699
+ }
2700
+ if (heartbeatInterval !== null) {
2701
+ window.clearInterval(heartbeatInterval);
2702
+ heartbeatInterval = null;
2703
+ }
2704
+ reject(error);
2705
+ };
2706
+ try {
2707
+ if (!channel) {
2708
+ channel = new BroadcastChannel(channelName);
2709
+ let otherTabActive = false;
2710
+ channel.onmessage = (e) => {
2711
+ let message = null;
2712
+ if (isCoordinationMessage(e.data)) {
2713
+ message = e.data;
2714
+ } else if (typeof e.data === "string" && ["ping", "pong", "takeover"].includes(e.data)) {
2715
+ message = { type: e.data };
2716
+ }
2717
+ if (!message)
2718
+ return;
2719
+ if (channelToken && message.token !== channelToken)
2720
+ return;
2721
+ if (message.type === "ping") {
2722
+ channel?.postMessage(createCoordinationMessage("pong", channelToken));
2723
+ } else if (message.type === "pong") {
2724
+ otherTabActive = true;
2725
+ } else if (message.type === "takeover") {
2726
+ teardownWebContainer();
2727
+ }
2728
+ };
2729
+ if (options?.takeover) {
2730
+ channel.postMessage(createCoordinationMessage("takeover", channelToken));
2731
+ await new Promise((r) => window.setTimeout(r, 250));
2732
+ } else {
2733
+ channel.postMessage(createCoordinationMessage("ping", channelToken));
2734
+ await new Promise((r) => window.setTimeout(r, 5000));
2735
+ if (otherTabActive) {
2736
+ throw new WebContainerTabBlockedError;
2737
+ }
2738
+ }
2739
+ heartbeatInterval = window.setInterval(() => {
2740
+ channel?.postMessage(createCoordinationMessage("ping", channelToken));
2741
+ }, 2000);
2742
+ }
2743
+ const timeoutPromise = new Promise((_, rejectTimeout) => {
2744
+ window.setTimeout(() => {
2745
+ rejectTimeout(new WebContainerBootTimeoutError);
2746
+ }, BOOT_TIMEOUT_MS);
2747
+ });
2748
+ const booted = await Promise.race([
2749
+ bootWithCredentiallessIframe(),
2750
+ timeoutPromise
2751
+ ]);
2752
+ if (settled) {
2753
+ booted.teardown();
2754
+ return;
2755
+ }
2756
+ settled = true;
2757
+ instance = booted;
2758
+ resolve(instance);
2759
+ } catch (e) {
2760
+ fail(e);
2761
+ }
2762
+ });
2763
+ return bootPromise2;
2764
+ }
2765
+ function teardownWebContainer() {
2766
+ if (instance) {
2767
+ instance.teardown();
2768
+ instance = null;
2769
+ }
2770
+ bootPromise2 = null;
2771
+ if (channel) {
2772
+ channel.close();
2773
+ channel = null;
2774
+ }
2775
+ if (heartbeatInterval !== null) {
2776
+ window.clearInterval(heartbeatInterval);
2777
+ heartbeatInterval = null;
2778
+ }
2779
+ }
2780
+ function getWebContainer() {
2781
+ return instance;
2782
+ }
2783
+ var instance = null, bootPromise2 = null, channel = null, heartbeatInterval = null, BOOT_TIMEOUT_MS = 45000, DEFAULT_CHANNEL_NAME, WebContainerTabBlockedError, WebContainerBootTimeoutError;
2784
+ var init_boot = __esm(() => {
2785
+ init_dist();
2786
+ DEFAULT_CHANNEL_NAME = typeof window !== "undefined" ? `vibe-ide-instance:${window.location.pathname}` : "vibe-ide-instance";
2787
+ WebContainerTabBlockedError = class WebContainerTabBlockedError extends Error {
2788
+ constructor() {
2789
+ super("Web IDE is already active in another tab. Close it or take over the session.");
2790
+ this.name = "WebContainerTabBlockedError";
2791
+ }
2792
+ };
2793
+ WebContainerBootTimeoutError = class WebContainerBootTimeoutError extends Error {
2794
+ constructor() {
2795
+ super("WebContainer boot timed out. This is often caused by blocked third-party storage/cookies or aggressive browser privacy settings for StackBlitz/WebContainer domains.");
2796
+ this.name = "WebContainerBootTimeoutError";
2797
+ }
2798
+ };
2799
+ });
2800
+
2801
+ // src/core/bridge-shim.ts
2802
+ var BRIDGE_DEV_SHIM_SOURCE = `
2803
+ (async function() {
2804
+ function waitForBridgeInit() {
2805
+ return new Promise((resolve) => {
2806
+ window.addEventListener('message', (e) => {
2807
+ if (e.data?.type === 'vibe:init') {
2808
+ resolve({ port: e.ports[0], previewSessionId: e.data.previewSessionId, bootstrap: e.data.bootstrap });
2809
+ }
2810
+ });
2811
+ });
2812
+ }
2813
+
2814
+ const { port, previewSessionId, bootstrap } = await waitForBridgeInit();
2815
+ const effectivePermissions = new Set(Array.isArray(bootstrap?.effectivePermissions) ? bootstrap.effectivePermissions : []);
2816
+ let currentBootstrap = bootstrap || {};
2817
+
2818
+ function sendOneWay(capability, method, args) {
2819
+ port.postMessage({ id: crypto.randomUUID(), previewSessionId, capability, method, args });
2820
+ }
2821
+
2822
+ function sendRequest(capability, method, args) {
2823
+ const id = crypto.randomUUID();
2824
+ return new Promise((resolve, reject) => {
2825
+ const handler = (e) => {
2826
+ if (e.data.id === id) {
2827
+ port.removeEventListener('message', handler);
2828
+ if (e.data.error) reject(new Error(e.data.error));
2829
+ else resolve(e.data.result);
2830
+ }
2831
+ };
2832
+ port.addEventListener('message', handler);
2833
+ port.start();
2834
+ port.postMessage({ id, previewSessionId, capability, method, args });
2835
+ });
2836
+ }
2837
+
2838
+ function sendStream(capability, method, args, onChunk) {
2839
+ const id = crypto.randomUUID();
2840
+ let isDone = false;
2841
+ let stopEventRegistration = () => {};
2842
+
2843
+ const promise = new Promise((resolve, reject) => {
2844
+ const handler = (e) => {
2845
+ if (e.data.id === id) {
2846
+ if (e.data.error) {
2847
+ port.removeEventListener('message', handler);
2848
+ reject(new Error(e.data.error));
2849
+ } else if (e.data.chunk !== undefined) {
2850
+ onChunk(e.data.chunk);
2851
+ } else if (e.data.subscribed) {
2852
+ resolve({ subscriptionId: id, unsubscribe: stopEventRegistration });
2853
+ } else if (e.data.done || e.data.result !== undefined) {
2854
+ port.removeEventListener('message', handler);
2855
+ isDone = true;
2856
+ resolve(e.data.result);
2857
+ }
2858
+ }
2859
+ };
2860
+ port.addEventListener('message', handler);
2861
+ port.start();
2862
+ port.postMessage({ id, previewSessionId, capability, method, args });
2863
+
2864
+ stopEventRegistration = () => {
2865
+ if (!isDone) {
2866
+ port.removeEventListener('message', handler);
2867
+ port.postMessage({
2868
+ id: crypto.randomUUID(),
2869
+ previewSessionId,
2870
+ capability,
2871
+ method: 'unsubscribe',
2872
+ args: [id],
2873
+ });
2874
+ }
2875
+ };
2876
+ });
2877
+
2878
+ return { promise, unsubscribe: stopEventRegistration };
2879
+ }
2880
+
2881
+ /* ── Capability factories ─────────────────────────────────────────────
2882
+ * Each factory returns the namespace object for a single capability.
2883
+ * Only capabilities present in effectivePermissions are assigned to the
2884
+ * bridge object, so unpermitted ones remain undefined and optional
2885
+ * chaining (e.g. bridge.auth?.getUser()) short-circuits safely.
2886
+ * ──────────────────────────────────────────────────────────────────── */
2887
+
2888
+ const capabilityFactories = {
2889
+ auth: () => ({
2890
+ getUser: () => {
2891
+ if (!currentBootstrap.user) {
2892
+ throw new Error('auth.getUser() is unavailable in this preview');
2893
+ }
2894
+ return currentBootstrap.user;
2895
+ },
2896
+ getToken: async () => { throw new Error("auth.getToken() is unavailable in dev preview"); }
2897
+ }),
2898
+ theme: () => ({
2899
+ current: () => currentBootstrap.theme
2900
+ }),
2901
+ toast: () => ({
2902
+ show: (msg, opts) => sendOneWay('toast', 'show', [msg, opts])
2903
+ }),
2904
+ api: () => ({
2905
+ fetch: async (ep, opts) => {
2906
+ const res = await sendRequest('api', 'fetch', [ep, opts]);
2907
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers: res.headers });
2908
+ }
2909
+ }),
2910
+ storage: () => ({
2911
+ get: (key) => sendRequest('storage', 'get', [key]),
2912
+ set: (key, val) => sendRequest('storage', 'set', [key, val]),
2913
+ remove: (key) => sendRequest('storage', 'remove', [key]),
2914
+ keys: () => sendRequest('storage', 'keys', [])
2915
+ }),
2916
+ navigation: () => ({
2917
+ navigate: (path) => sendOneWay('navigation', 'navigate', [path])
2918
+ }),
2919
+ events: () => ({
2920
+ emit: (evt, data) => sendOneWay('events', 'emit', [evt, data]),
2921
+ on: (evt, cb) => {
2922
+ const stream = sendStream('events', 'on', [evt], (chunk) => {
2923
+ cb(chunk);
2924
+ });
2925
+ return stream.unsubscribe;
2926
+ },
2927
+ once: (evt, cb) => {
2928
+ let unsub = () => {};
2929
+ const wrappedCb = (chunk) => { unsub(); cb(chunk); };
2930
+ const stream = sendStream('events', 'once', [evt], wrappedCb);
2931
+ unsub = stream.unsubscribe;
2932
+ return unsub;
2933
+ }
2934
+ }),
2935
+ chat: () => ({
2936
+ send: function(serverUrl, parts, options) {
2937
+ const payload = {
2938
+ serverUrl,
2939
+ parts,
2940
+ options: options ? {
2941
+ contextId: options.contextId,
2942
+ messageId: options.messageId,
2943
+ metadata: options.metadata,
2944
+ } : undefined,
2945
+ };
2946
+ let buffer = [];
2947
+ let error = null;
2948
+ let done = false;
2949
+ let resolvers = [];
2950
+ const stream = sendStream('chat', 'send', [payload], (chunk) => {
2951
+ buffer.push(chunk);
2952
+ if (resolvers.length > 0) resolvers.shift()();
2953
+ });
2954
+
2955
+ stream.promise.then((res) => {
2956
+ done = true;
2957
+ if (resolvers.length > 0) resolvers.shift()();
2958
+ }).catch(err => {
2959
+ error = err;
2960
+ done = true;
2961
+ if (resolvers.length > 0) resolvers.shift()();
2962
+ });
2963
+ const iterator = (async function*() {
2964
+ while (true) {
2965
+ if (buffer.length > 0) yield buffer.shift();
2966
+ else if (error) throw error;
2967
+ else if (done) break;
2968
+ else await new Promise(r => resolvers.push(r));
2969
+ }
2970
+ })();
2971
+
2972
+ return {
2973
+ [Symbol.asyncIterator]: () => iterator,
2974
+ abort: () => stream.unsubscribe(),
2975
+ };
2976
+ }
2977
+ })
2978
+ };
2979
+
2980
+ /* ── Build bridge object from permitted capabilities only ──────────── */
2981
+
2982
+ function buildBridge() {
2983
+ const bridge = {};
2984
+ for (const cap of effectivePermissions) {
2985
+ if (capabilityFactories[cap]) {
2986
+ bridge[cap] = capabilityFactories[cap]();
2987
+ }
2988
+ }
2989
+ return bridge;
2990
+ }
2991
+
2992
+ window.__VIBE_BRIDGE_DEV__ = buildBridge();
2993
+
2994
+ /* ── Handle runtime permission changes ─────────────────────────────── */
2995
+
2996
+ window.addEventListener('message', (e) => {
2997
+ if (e.data?.type === 'vibe:permissions') {
2998
+ effectivePermissions.clear();
2999
+ const nextPermissions = Array.isArray(e.data.effectivePermissions) ? e.data.effectivePermissions : [];
3000
+ nextPermissions.forEach((capability) => effectivePermissions.add(capability));
3001
+ currentBootstrap = e.data.bootstrap || currentBootstrap;
3002
+ window.__VIBE_BRIDGE_DEV__ = buildBridge();
3003
+ }
3004
+ });
3005
+
3006
+ window.dispatchEvent(new Event("vibe-bridge-ready"));
3007
+ })();
3008
+ `;
3009
+
3010
+ // src/core/inject-shim.ts
3011
+ var exports_inject_shim = {};
3012
+ __export(exports_inject_shim, {
3013
+ injectDevShim: () => injectDevShim
3014
+ });
3015
+ async function injectDevShim(instance2) {
3016
+ try {
3017
+ await instance2.fs.mkdir(".ide");
3018
+ } catch {}
3019
+ await instance2.fs.writeFile(".ide/vibe-bridge-dev.js", BRIDGE_DEV_SHIM_SOURCE);
3020
+ }
3021
+ var init_inject_shim = () => {};
3022
+
3023
+ // src/core/process.ts
3024
+ var exports_process = {};
3025
+ __export(exports_process, {
3026
+ spawnWithOutput: () => spawnWithOutput,
3027
+ spawnDevServer: () => spawnDevServer,
3028
+ DevServerStartupError: () => DevServerStartupError
3029
+ });
3030
+ async function spawnWithOutput(instance2, command, args, options) {
3031
+ const process = await instance2.spawn(command, args);
3032
+ let buffer = "";
3033
+ let batched = false;
3034
+ let totalOutput = 0;
3035
+ const MAX_TOTAL = 1 * 1024 * 1024;
3036
+ const MAX_FLUSH = 50 * 1024;
3037
+ let isKilled = false;
3038
+ const stream = new WritableStream({
3039
+ write(data) {
3040
+ if (isKilled)
3041
+ return;
3042
+ totalOutput += data.length;
3043
+ if (totalOutput > MAX_TOTAL) {
3044
+ console.error(`Process ${command} output exceeded 1MB cap.`);
3045
+ isKilled = true;
3046
+ process.kill();
3047
+ return;
3048
+ }
3049
+ buffer += data;
3050
+ if (buffer.length > MAX_FLUSH && options?.onChunk) {
3051
+ options.onChunk(buffer);
3052
+ buffer = "";
3053
+ return;
3054
+ }
3055
+ if (options?.onChunk && !batched && buffer.length > 0) {
3056
+ batched = true;
3057
+ requestAnimationFrame(() => {
3058
+ if (buffer.length > 0 && !isKilled) {
3059
+ options.onChunk(buffer);
3060
+ buffer = "";
3061
+ }
3062
+ batched = false;
3063
+ });
3064
+ }
3065
+ }
3066
+ });
3067
+ process.output.pipeTo(stream).catch(() => {});
3068
+ const exitCodePromise = new Promise((resolve) => {
3069
+ const timeoutMs = options?.timeout ?? 120000;
3070
+ let timeoutId;
3071
+ if (timeoutMs > 0) {
3072
+ timeoutId = window.setTimeout(() => {
3073
+ isKilled = true;
3074
+ process.kill();
3075
+ }, timeoutMs);
3076
+ }
3077
+ process.exit.then((code) => {
3078
+ if (timeoutId)
3079
+ window.clearTimeout(timeoutId);
3080
+ if (buffer && options?.onChunk && !isKilled) {
3081
+ options.onChunk(buffer);
3082
+ buffer = "";
3083
+ }
3084
+ resolve(isKilled ? 143 : code);
3085
+ });
3086
+ });
3087
+ return {
3088
+ exitCode: exitCodePromise,
3089
+ kill: () => {
3090
+ isKilled = true;
3091
+ process.kill();
3092
+ }
3093
+ };
3094
+ }
3095
+ async function spawnDevServer(instance2, options) {
3096
+ const process = await instance2.spawn("npm", ["run", "dev"]);
3097
+ let output = "";
3098
+ let resolved = false;
3099
+ let timeoutId = null;
3100
+ const serverReadyPromise = new Promise((resolve, reject) => {
3101
+ const unsubscribe = instance2.on("server-ready", (_port, url) => {
3102
+ resolved = true;
3103
+ if (timeoutId !== null) {
3104
+ window.clearTimeout(timeoutId);
3105
+ }
3106
+ unsubscribe();
3107
+ resolve(url);
3108
+ });
3109
+ const timeoutMs = options?.timeout ?? 120000;
3110
+ if (timeoutMs > 0) {
3111
+ timeoutId = window.setTimeout(() => {
3112
+ if (resolved) {
3113
+ return;
3114
+ }
3115
+ process.kill();
3116
+ unsubscribe();
3117
+ reject(new DevServerStartupError("Timed out waiting for the dev server to become ready.", output));
3118
+ }, timeoutMs);
3119
+ }
3120
+ process.exit.then((code) => {
3121
+ if (resolved) {
3122
+ return;
3123
+ }
3124
+ if (timeoutId !== null) {
3125
+ window.clearTimeout(timeoutId);
3126
+ }
3127
+ unsubscribe();
3128
+ reject(new DevServerStartupError(`Dev server exited before becoming ready (exit code ${code}).`, output));
3129
+ });
3130
+ });
3131
+ process.output.pipeTo(new WritableStream({
3132
+ write(data) {
3133
+ output += data;
3134
+ if (output.length > 64 * 1024) {
3135
+ output = output.slice(output.length - 64 * 1024);
3136
+ }
3137
+ options?.onOutput?.(data);
3138
+ }
3139
+ })).catch(() => {});
3140
+ return {
3141
+ url: serverReadyPromise,
3142
+ kill: () => process.kill(),
3143
+ output: process.output
3144
+ };
3145
+ }
3146
+ var DevServerStartupError;
3147
+ var init_process = __esm(() => {
3148
+ DevServerStartupError = class DevServerStartupError extends Error {
3149
+ output;
3150
+ constructor(message, output) {
3151
+ super(message);
3152
+ this.name = "DevServerStartupError";
3153
+ this.output = output;
3154
+ }
3155
+ };
3156
+ });
3157
+
3158
+ // src/core/install.ts
3159
+ var exports_install = {};
3160
+ __export(exports_install, {
3161
+ installDependencies: () => installDependencies,
3162
+ computeInstallFingerprint: () => computeInstallFingerprint
3163
+ });
3164
+ async function computeInstallFingerprint(lockfile, nodeVersion, npmVersion, installMode, templateId) {
3165
+ const input = `${lockfile}:${nodeVersion}:${npmVersion}:${installMode}:${templateId}`;
3166
+ const enc = new TextEncoder().encode(input);
3167
+ const hash = await crypto.subtle.digest("SHA-256", enc);
3168
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
3169
+ }
3170
+ async function installDependencies(instance2, options) {
3171
+ let hasLockfile = false;
3172
+ let pkgLock = "";
3173
+ try {
3174
+ pkgLock = await instance2.fs.readFile("/package-lock.json", "utf8");
3175
+ hasLockfile = !!pkgLock;
3176
+ } catch {
3177
+ hasLockfile = false;
3178
+ }
3179
+ const currentFingerprint = await computeInstallFingerprint(pkgLock, "20.x", "10.x", hasLockfile ? "ci" : "install", options?.templateId || "react");
3180
+ if (successfulFingerprints.get(instance2) === currentFingerprint) {
3181
+ return { skipped: true, exitCode: 0, fingerprint: currentFingerprint };
3182
+ }
3183
+ const args = hasLockfile ? ["ci", "--prefer-offline"] : ["install", "--prefer-offline"];
3184
+ const proc = await spawnWithOutput(instance2, "npm", args, {
3185
+ onChunk: options?.onOutput
3186
+ });
3187
+ const code = await proc.exitCode;
3188
+ if (code === 0) {
3189
+ successfulFingerprints.set(instance2, currentFingerprint);
3190
+ }
3191
+ return { skipped: false, exitCode: code, fingerprint: currentFingerprint };
3192
+ }
3193
+ var successfulFingerprints;
3194
+ var init_install = __esm(() => {
3195
+ init_process();
3196
+ successfulFingerprints = new WeakMap;
3197
+ });
3198
+
3199
+ // src/react/provider.tsx
3200
+ import {
3201
+ createContext,
3202
+ useCallback as useCallback2,
3203
+ useEffect as useEffect2,
3204
+ useMemo,
3205
+ useRef as useRef2,
3206
+ useState as useState2
3207
+ } from "react";
3208
+ import { jsxDEV } from "react/jsx-dev-runtime";
3209
+ function appendBootLog(current, chunk) {
3210
+ const next = current + chunk;
3211
+ return next.length <= MAX_BOOT_LOG_LENGTH ? next : next.slice(next.length - MAX_BOOT_LOG_LENGTH);
3212
+ }
3213
+ function formatBootError(message, output) {
3214
+ const trimmedOutput = output.trim();
3215
+ if (!trimmedOutput) {
3216
+ return new Error(message);
3217
+ }
3218
+ const excerpt = trimmedOutput.slice(-4000);
3219
+ return new Error(`${message}
3220
+
3221
+ Recent output:
3222
+ ${excerpt}`);
3223
+ }
3224
+ async function withTimeout(promise, timeoutMs, message, getOutput) {
3225
+ let timeoutId = null;
3226
+ const timeoutPromise = new Promise((_, reject) => {
3227
+ timeoutId = window.setTimeout(() => {
3228
+ reject(formatBootError(message, getOutput()));
3229
+ }, timeoutMs);
3230
+ });
3231
+ try {
3232
+ return await Promise.race([promise, timeoutPromise]);
3233
+ } finally {
3234
+ if (timeoutId !== null) {
3235
+ window.clearTimeout(timeoutId);
3236
+ }
3237
+ }
3238
+ }
3239
+ function buildDefaultPolicy(override) {
3240
+ return {
3241
+ autoApproved: override?.autoApproved ?? DEFAULT_AUTO_APPROVED,
3242
+ requestApproval: override?.requestApproval,
3243
+ allowNavigation: override?.allowNavigation,
3244
+ allowApiRequest: override?.allowApiRequest,
3245
+ allowChatRequest: override?.allowChatRequest
3246
+ };
3247
+ }
3248
+ function VibeIdeProvider({
3249
+ draftId,
3250
+ templateId,
3251
+ bridge,
3252
+ chat,
3253
+ previewPolicy: previewPolicyProp,
3254
+ persistence,
3255
+ approval,
3256
+ templateVars,
3257
+ initialTree,
3258
+ bootOptions,
3259
+ children
3260
+ }) {
3261
+ const [status, setStatus] = useState2("idle");
3262
+ const [error, setError] = useState2(null);
3263
+ const [devServerUrl, setDevServerUrl] = useState2(null);
3264
+ const [bootNonce, setBootNonce] = useState2(0);
3265
+ const [manifestPermissions, setManifestPermissions] = useState2([]);
3266
+ const [activeRightPane, setActiveRightPane] = useState2("preview");
3267
+ const [refreshNonce, setRefreshNonce] = useState2(0);
3268
+ const [previewSessionNonce, setPreviewSessionNonce] = useState2(0);
3269
+ const instanceRef = useRef2(null);
3270
+ const coalescerRef = useRef2(null);
3271
+ const processRegistryRef = useRef2(null);
3272
+ const serverRef = useRef2(null);
3273
+ const defaultBootOptionsRef = useRef2(bootOptions ?? {});
3274
+ const bootOptionsRef = useRef2(bootOptions ?? {});
3275
+ const previewChannelRef = useRef2(null);
3276
+ const bridgeRef = useRef2(bridge);
3277
+ const flushTimerRef = useRef2(null);
3278
+ useEffect2(() => {
3279
+ bridgeRef.current = bridge;
3280
+ }, [bridge]);
3281
+ useEffect2(() => {
3282
+ defaultBootOptionsRef.current = bootOptions ?? {};
3283
+ }, [bootOptions]);
3284
+ useEffect2(() => {
3285
+ if (!draftId)
3286
+ return;
3287
+ let mounted = true;
3288
+ async function boot() {
3289
+ setStatus("booting");
3290
+ setError(null);
3291
+ setDevServerUrl(null);
3292
+ let bootOutput = "";
3293
+ let bootStage = "booting WebContainer";
3294
+ let unsubscribeFromWebContainerError = null;
3295
+ const captureBootOutput = (chunk) => {
3296
+ bootOutput = appendBootLog(bootOutput, chunk);
3297
+ };
3298
+ try {
3299
+ const { bootWebContainer: bootWebContainer2 } = await Promise.resolve().then(() => (init_boot(), exports_boot));
3300
+ const { injectDevShim: injectDevShim2 } = await Promise.resolve().then(() => (init_inject_shim(), exports_inject_shim));
3301
+ const { installDependencies: installDependencies2 } = await Promise.resolve().then(() => (init_install(), exports_install));
3302
+ const { spawnDevServer: spawnDevServer2 } = await Promise.resolve().then(() => (init_process(), exports_process));
3303
+ const wb = await withTimeout(bootWebContainer2(bootOptionsRef.current), BOOT_SEQUENCE_TIMEOUT_MS, `Timed out while ${bootStage}.`, () => bootOutput);
3304
+ bootOptionsRef.current = { ...defaultBootOptionsRef.current };
3305
+ if (!mounted)
3306
+ return;
3307
+ unsubscribeFromWebContainerError = wb.on("error", (event) => {
3308
+ captureBootOutput(`[webcontainer] ${event.message}
3309
+ `);
3310
+ });
3311
+ bootStage = "mounting the project template";
3312
+ const tree = initialTree ?? getVibeIdeTemplateTree(templateId, templateVars);
3313
+ await withTimeout(wb.mount(tree), BOOT_SEQUENCE_TIMEOUT_MS, `Timed out while ${bootStage}.`, () => bootOutput);
3314
+ instanceRef.current = wb;
3315
+ processRegistryRef.current = new ProcessRegistry(wb);
3316
+ coalescerRef.current = new WriteCoalescer(wb, {
3317
+ onFlush: () => {
3318
+ if (flushTimerRef.current !== null) {
3319
+ window.clearTimeout(flushTimerRef.current);
3320
+ }
3321
+ flushTimerRef.current = window.setTimeout(() => {
3322
+ flushTimerRef.current = null;
3323
+ setRefreshNonce((n) => n + 1);
3324
+ }, 500);
3325
+ }
3326
+ });
3327
+ bootStage = "injecting the preview bridge";
3328
+ await injectDevShim2(wb);
3329
+ bootStage = "installing dependencies";
3330
+ const installResult = await installDependencies2(wb, {
3331
+ templateId,
3332
+ onOutput: captureBootOutput
3333
+ });
3334
+ if (installResult.exitCode !== 0) {
3335
+ throw formatBootError(`Dependency installation failed with exit code ${installResult.exitCode}.`, bootOutput);
3336
+ }
3337
+ bootStage = "starting the dev server";
3338
+ const server = await spawnDevServer2(wb, { onOutput: captureBootOutput });
3339
+ serverRef.current = server;
3340
+ const url = await withTimeout(server.url, BOOT_SEQUENCE_TIMEOUT_MS, `Timed out while ${bootStage}.`, () => bootOutput);
3341
+ unsubscribeFromWebContainerError?.();
3342
+ unsubscribeFromWebContainerError = null;
3343
+ if (!mounted) {
3344
+ server.kill();
3345
+ return;
3346
+ }
3347
+ setDevServerUrl(url);
3348
+ setPreviewSessionNonce((n) => n + 1);
3349
+ setStatus("ready");
3350
+ } catch (e) {
3351
+ if (!mounted)
3352
+ return;
3353
+ const err = e instanceof Error ? bootOutput && !e.message.includes("Recent output:") ? formatBootError(e.message, bootOutput) : e : formatBootError(String(e), bootOutput);
3354
+ unsubscribeFromWebContainerError?.();
3355
+ if (err.name === "WebContainerTabBlockedError") {
3356
+ setStatus("blocked");
3357
+ } else {
3358
+ setStatus("error");
3359
+ }
3360
+ setError(err);
3361
+ }
3362
+ }
3363
+ boot();
3364
+ return () => {
3365
+ mounted = false;
3366
+ serverRef.current?.kill();
3367
+ serverRef.current = null;
3368
+ processRegistryRef.current?.destroy();
3369
+ processRegistryRef.current = null;
3370
+ Promise.resolve().then(() => (init_boot(), exports_boot)).then((m) => m.teardownWebContainer());
3371
+ coalescerRef.current?.destroy();
3372
+ if (flushTimerRef.current !== null) {
3373
+ window.clearTimeout(flushTimerRef.current);
3374
+ flushTimerRef.current = null;
3375
+ }
3376
+ };
3377
+ }, [draftId, bootNonce, templateId, templateVars, initialTree]);
3378
+ useEffect2(() => {
3379
+ const instance2 = instanceRef.current;
3380
+ if (!instance2) {
3381
+ setManifestPermissions([]);
3382
+ return;
3383
+ }
3384
+ let cancelled = false;
3385
+ (async () => {
3386
+ try {
3387
+ const raw = await instance2.fs.readFile("/manifest.json", "utf8");
3388
+ if (!raw || cancelled)
3389
+ return;
3390
+ const parsed = JSON.parse(raw);
3391
+ setManifestPermissions(Array.isArray(parsed.permissions) ? parsed.permissions : []);
3392
+ } catch {
3393
+ if (!cancelled)
3394
+ setManifestPermissions([]);
3395
+ }
3396
+ })();
3397
+ return () => {
3398
+ cancelled = true;
3399
+ };
3400
+ }, [devServerUrl]);
3401
+ const restart = useCallback2(async (options) => {
3402
+ const { teardownWebContainer: teardownWebContainer2 } = await Promise.resolve().then(() => (init_boot(), exports_boot));
3403
+ serverRef.current?.kill();
3404
+ serverRef.current = null;
3405
+ processRegistryRef.current?.destroy();
3406
+ processRegistryRef.current = null;
3407
+ teardownWebContainer2();
3408
+ bootOptionsRef.current = {
3409
+ ...defaultBootOptionsRef.current,
3410
+ ...options ?? {}
3411
+ };
3412
+ setStatus("booting");
3413
+ setError(null);
3414
+ setDevServerUrl(null);
3415
+ setPreviewSessionNonce((n) => n + 1);
3416
+ instanceRef.current = null;
3417
+ setBootNonce((n) => n + 1);
3418
+ }, []);
3419
+ const refresh = useCallback2(() => {
3420
+ setRefreshNonce((n) => n + 1);
3421
+ }, []);
3422
+ const autosave = useAutosave({
3423
+ draftId,
3424
+ instance: instanceRef.current,
3425
+ coalescer: coalescerRef.current,
3426
+ enabled: status === "ready",
3427
+ persistence
3428
+ });
3429
+ const previewPolicy = useMemo(() => buildDefaultPolicy(previewPolicyProp), [previewPolicyProp]);
3430
+ const contextValue = useMemo(() => ({
3431
+ draftId,
3432
+ templateId,
3433
+ bridge,
3434
+ chat: chat ?? null,
3435
+ previewPolicy,
3436
+ approval: approval ?? {},
3437
+ webcontainer: {
3438
+ status,
3439
+ instance: instanceRef.current,
3440
+ devServerUrl,
3441
+ error,
3442
+ restart,
3443
+ refresh,
3444
+ refreshNonce,
3445
+ previewSessionNonce,
3446
+ coalescer: coalescerRef.current
3447
+ },
3448
+ processRegistry: processRegistryRef.current,
3449
+ autosave,
3450
+ manifestPermissions,
3451
+ activeRightPane,
3452
+ setActiveRightPane,
3453
+ previewChannelRef,
3454
+ bridgeRef
3455
+ }), [
3456
+ draftId,
3457
+ templateId,
3458
+ bridge,
3459
+ chat,
3460
+ previewPolicy,
3461
+ approval,
3462
+ status,
3463
+ devServerUrl,
3464
+ error,
3465
+ restart,
3466
+ refresh,
3467
+ refreshNonce,
3468
+ previewSessionNonce,
3469
+ autosave,
3470
+ manifestPermissions,
3471
+ activeRightPane
3472
+ ]);
3473
+ return /* @__PURE__ */ jsxDEV(VibeIdeContext.Provider, {
3474
+ value: contextValue,
3475
+ children
3476
+ }, undefined, false, undefined, this);
3477
+ }
3478
+ var VibeIdeContext, MAX_BOOT_LOG_LENGTH, BOOT_SEQUENCE_TIMEOUT_MS = 180000, DEFAULT_AUTO_APPROVED;
3479
+ var init_provider = __esm(() => {
3480
+ init_filesystem();
3481
+ init_generated();
3482
+ init_use_autosave();
3483
+ VibeIdeContext = createContext(null);
3484
+ MAX_BOOT_LOG_LENGTH = 64 * 1024;
3485
+ DEFAULT_AUTO_APPROVED = [
3486
+ "toast",
3487
+ "theme"
3488
+ ];
3489
+ });
3490
+
3491
+ // src/react/use-vibe-ide.ts
3492
+ import { useContext } from "react";
3493
+ function useVibeIde() {
3494
+ const ctx = useContext(VibeIdeContext);
3495
+ if (!ctx) {
3496
+ throw new Error("useVibeIde must be used within a <VibeIdeProvider>.");
3497
+ }
3498
+ return ctx;
3499
+ }
3500
+ var init_use_vibe_ide = __esm(() => {
3501
+ init_provider();
3502
+ });
3503
+
3504
+ // src/react/file-explorer.tsx
3505
+ var exports_file_explorer = {};
3506
+ __export(exports_file_explorer, {
3507
+ FileExplorer: () => FileExplorer
3508
+ });
3509
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
3510
+ import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
3511
+ function getFileIcon(name) {
3512
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
3513
+ return EXTENSION_ICONS[ext] ?? "F";
3514
+ }
3515
+ function TreeNode({
3516
+ entry,
3517
+ depth,
3518
+ expandedDirs,
3519
+ selectedFile,
3520
+ onToggleDir,
3521
+ onSelectFile,
3522
+ onContextMenu
3523
+ }) {
3524
+ const isDir = entry.type === "directory";
3525
+ const isExpanded = expandedDirs.has(entry.path);
3526
+ const isSelected = selectedFile === entry.path;
3527
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
3528
+ children: [
3529
+ /* @__PURE__ */ jsxDEV2("button", {
3530
+ className: "flex w-full items-center gap-1.5 py-0.5 text-left text-xs",
3531
+ style: {
3532
+ paddingLeft: `${depth * 12 + 8}px`,
3533
+ background: isSelected ? "var(--ide-accent-soft)" : "transparent",
3534
+ color: isSelected ? "var(--ide-accent)" : "var(--ide-text-secondary)",
3535
+ borderRadius: "2px"
3536
+ },
3537
+ onClick: () => {
3538
+ if (isDir) {
3539
+ onToggleDir(entry.path);
3540
+ } else {
3541
+ onSelectFile(entry.path);
3542
+ }
3543
+ },
3544
+ onContextMenu: (e) => onContextMenu(e, entry),
3545
+ title: entry.path,
3546
+ children: [
3547
+ /* @__PURE__ */ jsxDEV2("span", {
3548
+ className: "inline-flex w-3 shrink-0 justify-center",
3549
+ style: { opacity: isDir ? 1 : 0 },
3550
+ children: /* @__PURE__ */ jsxDEV2("svg", {
3551
+ width: "8",
3552
+ height: "8",
3553
+ viewBox: "0 0 8 8",
3554
+ fill: "currentColor",
3555
+ style: {
3556
+ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
3557
+ transition: "transform 100ms"
3558
+ },
3559
+ children: /* @__PURE__ */ jsxDEV2("path", {
3560
+ d: "M2 1 L6 4 L2 7 Z"
3561
+ }, undefined, false, undefined, this)
3562
+ }, undefined, false, undefined, this)
3563
+ }, undefined, false, undefined, this),
3564
+ /* @__PURE__ */ jsxDEV2("span", {
3565
+ className: "inline-flex w-5 shrink-0 justify-center text-[9px] font-bold",
3566
+ style: {
3567
+ color: isDir ? "var(--ide-accent-muted)" : "var(--ide-text-tertiary)"
3568
+ },
3569
+ children: isDir ? isExpanded ? "▾" : "▸" : getFileIcon(entry.name)
3570
+ }, undefined, false, undefined, this),
3571
+ /* @__PURE__ */ jsxDEV2("span", {
3572
+ className: "truncate",
3573
+ children: entry.name
3574
+ }, undefined, false, undefined, this)
3575
+ ]
3576
+ }, undefined, true, undefined, this),
3577
+ isDir && isExpanded && entry.children && /* @__PURE__ */ jsxDEV2(Fragment, {
3578
+ children: [
3579
+ entry.children.map((child) => /* @__PURE__ */ jsxDEV2(TreeNode, {
3580
+ entry: child,
3581
+ depth: depth + 1,
3582
+ expandedDirs,
3583
+ selectedFile,
3584
+ onToggleDir,
3585
+ onSelectFile,
3586
+ onContextMenu
3587
+ }, child.path, false, undefined, this)),
3588
+ entry.children.length === 0 && /* @__PURE__ */ jsxDEV2("span", {
3589
+ className: "block py-0.5 text-[10px] italic",
3590
+ style: {
3591
+ paddingLeft: `${(depth + 1) * 12 + 8}px`,
3592
+ color: "var(--ide-text-tertiary)"
3593
+ },
3594
+ children: "(empty)"
3595
+ }, undefined, false, undefined, this)
3596
+ ]
3597
+ }, undefined, true, undefined, this)
3598
+ ]
3599
+ }, undefined, true, undefined, this);
3600
+ }
3601
+ function InlineInput({ defaultValue = "", onSubmit, onCancel, placeholder }) {
3602
+ const inputRef = useRef3(null);
3603
+ const [value, setValue] = useState3(defaultValue);
3604
+ useEffect3(() => {
3605
+ inputRef.current?.focus();
3606
+ inputRef.current?.select();
3607
+ }, []);
3608
+ return /* @__PURE__ */ jsxDEV2("input", {
3609
+ ref: inputRef,
3610
+ className: "ide-input w-full py-0.5 px-2 text-xs",
3611
+ style: { margin: "2px 8px", fontSize: "11px" },
3612
+ value,
3613
+ placeholder,
3614
+ onChange: (e) => setValue(e.target.value),
3615
+ onKeyDown: (e) => {
3616
+ if (e.key === "Enter" && value.trim()) {
3617
+ onSubmit(value.trim());
3618
+ } else if (e.key === "Escape") {
3619
+ onCancel();
3620
+ }
3621
+ },
3622
+ onBlur: () => {
3623
+ if (value.trim()) {
3624
+ onSubmit(value.trim());
3625
+ } else {
3626
+ onCancel();
3627
+ }
3628
+ }
3629
+ }, undefined, false, undefined, this);
3630
+ }
3631
+ function FileExplorer({
3632
+ onFileSelect,
3633
+ selectedFile: selectedFileProp = null,
3634
+ refreshNonce = 0
3635
+ }) {
3636
+ const { webcontainer } = useVibeIde();
3637
+ const instance2 = webcontainer.instance;
3638
+ const [tree, setTree] = useState3([]);
3639
+ const [expandedDirs, setExpandedDirs] = useState3(() => new Set(["/src"]));
3640
+ const [internalSelectedFile, setInternalSelectedFile] = useState3(null);
3641
+ const [loading, setLoading] = useState3(true);
3642
+ const [contextMenu, setContextMenu] = useState3(null);
3643
+ const [inlineAction, setInlineAction] = useState3(null);
3644
+ const selectedFile = selectedFileProp ?? internalSelectedFile;
3645
+ const loadTree = useCallback3(async () => {
3646
+ if (!instance2)
3647
+ return;
3648
+ try {
3649
+ const entries = await readTreeStructure(instance2);
3650
+ setTree(entries);
3651
+ } catch (e) {
3652
+ console.error("FileExplorer: failed to read tree", e);
3653
+ } finally {
3654
+ setLoading(false);
3655
+ }
3656
+ }, [instance2]);
3657
+ useEffect3(() => {
3658
+ loadTree();
3659
+ }, [loadTree, refreshNonce]);
3660
+ useEffect3(() => {
3661
+ const coalescer = webcontainer.coalescer;
3662
+ if (!coalescer)
3663
+ return;
3664
+ let pending = false;
3665
+ let timerId = null;
3666
+ const triggerRefresh = () => {
3667
+ if (pending)
3668
+ return;
3669
+ pending = true;
3670
+ timerId = window.setTimeout(() => {
3671
+ pending = false;
3672
+ loadTree();
3673
+ }, 500);
3674
+ };
3675
+ const unsubWrite = coalescer.onWrite(triggerRefresh);
3676
+ const unsubDelete = coalescer.onDelete(triggerRefresh);
3677
+ return () => {
3678
+ unsubWrite();
3679
+ unsubDelete();
3680
+ if (timerId !== null)
3681
+ window.clearTimeout(timerId);
3682
+ };
3683
+ }, [webcontainer.coalescer, loadTree]);
3684
+ const handleToggleDir = useCallback3((path) => {
3685
+ setExpandedDirs((prev) => {
3686
+ const next = new Set(prev);
3687
+ if (next.has(path)) {
3688
+ next.delete(path);
3689
+ } else {
3690
+ next.add(path);
3691
+ }
3692
+ return next;
3693
+ });
3694
+ }, []);
3695
+ const handleSelectFile = useCallback3((path) => {
3696
+ setInternalSelectedFile(path);
3697
+ onFileSelect?.(path);
3698
+ }, [onFileSelect]);
3699
+ const handleContextMenu = useCallback3((e, entry) => {
3700
+ e.preventDefault();
3701
+ setContextMenu({
3702
+ x: e.clientX,
3703
+ y: e.clientY,
3704
+ entry,
3705
+ parentPath: entry.type === "directory" ? entry.path : entry.path.split("/").slice(0, -1).join("/") || "/"
3706
+ });
3707
+ }, []);
3708
+ useEffect3(() => {
3709
+ if (!contextMenu)
3710
+ return;
3711
+ const handler = () => setContextMenu(null);
3712
+ window.addEventListener("click", handler);
3713
+ return () => window.removeEventListener("click", handler);
3714
+ }, [contextMenu]);
3715
+ const handleCreateFile = useCallback3((parentPath) => {
3716
+ setContextMenu(null);
3717
+ setExpandedDirs((prev) => new Set(prev).add(parentPath));
3718
+ setInlineAction({ type: "create-file", parentPath });
3719
+ }, []);
3720
+ const handleCreateDir = useCallback3((parentPath) => {
3721
+ setContextMenu(null);
3722
+ setExpandedDirs((prev) => new Set(prev).add(parentPath));
3723
+ setInlineAction({ type: "create-dir", parentPath });
3724
+ }, []);
3725
+ const handleRename = useCallback3((entry) => {
3726
+ setContextMenu(null);
3727
+ const parentPath = entry.path.split("/").slice(0, -1).join("/") || "/";
3728
+ setInlineAction({
3729
+ type: "rename",
3730
+ parentPath,
3731
+ currentName: entry.name,
3732
+ currentPath: entry.path
3733
+ });
3734
+ }, []);
3735
+ const handleDelete = useCallback3(async (entry) => {
3736
+ setContextMenu(null);
3737
+ if (!instance2)
3738
+ return;
3739
+ const confirmed = window.confirm(`Delete "${entry.name}"?`);
3740
+ if (!confirmed)
3741
+ return;
3742
+ try {
3743
+ await instance2.fs.rm(entry.path, { recursive: true });
3744
+ webcontainer.coalescer?.notifyDelete?.(entry.path);
3745
+ loadTree();
3746
+ } catch (e) {
3747
+ console.error("FileExplorer: delete failed", e);
3748
+ }
3749
+ }, [instance2, webcontainer.coalescer, loadTree]);
3750
+ const handleInlineSubmit = useCallback3(async (value) => {
3751
+ if (!instance2 || !inlineAction)
3752
+ return;
3753
+ try {
3754
+ if (inlineAction.type === "create-file") {
3755
+ const fullPath = inlineAction.parentPath === "/" ? `/${value}` : `${inlineAction.parentPath}/${value}`;
3756
+ sanitizePath(value);
3757
+ await instance2.fs.writeFile(fullPath, "");
3758
+ loadTree();
3759
+ handleSelectFile(fullPath);
3760
+ } else if (inlineAction.type === "create-dir") {
3761
+ const fullPath = inlineAction.parentPath === "/" ? `/${value}` : `${inlineAction.parentPath}/${value}`;
3762
+ sanitizePath(value);
3763
+ await instance2.fs.mkdir(fullPath, { recursive: true });
3764
+ loadTree();
3765
+ } else if (inlineAction.type === "rename" && inlineAction.currentPath) {
3766
+ const oldPath = inlineAction.currentPath;
3767
+ const newPath = inlineAction.parentPath === "/" ? `/${value}` : `${inlineAction.parentPath}/${value}`;
3768
+ sanitizePath(value);
3769
+ const content = await instance2.fs.readFile(oldPath, "utf8");
3770
+ await instance2.fs.writeFile(newPath, content);
3771
+ await instance2.fs.rm(oldPath);
3772
+ webcontainer.coalescer?.notifyDelete?.(oldPath);
3773
+ loadTree();
3774
+ if (selectedFile === oldPath) {
3775
+ handleSelectFile(newPath);
3776
+ }
3777
+ }
3778
+ } catch (e) {
3779
+ console.error("FileExplorer: inline action failed", e);
3780
+ } finally {
3781
+ setInlineAction(null);
3782
+ }
3783
+ }, [instance2, inlineAction, webcontainer.coalescer, loadTree, handleSelectFile, selectedFile]);
3784
+ if (loading && tree.length === 0) {
3785
+ return /* @__PURE__ */ jsxDEV2("div", {
3786
+ className: "flex h-full items-center justify-center",
3787
+ style: { color: "var(--ide-text-tertiary)" },
3788
+ children: /* @__PURE__ */ jsxDEV2("div", {
3789
+ className: "ide-spinner",
3790
+ style: { width: "1.25rem", height: "1.25rem" }
3791
+ }, undefined, false, undefined, this)
3792
+ }, undefined, false, undefined, this);
3793
+ }
3794
+ return /* @__PURE__ */ jsxDEV2("div", {
3795
+ className: "flex h-full flex-col overflow-hidden",
3796
+ style: { background: "var(--ide-bg)" },
3797
+ children: [
3798
+ /* @__PURE__ */ jsxDEV2("div", {
3799
+ className: "flex h-8 shrink-0 items-center justify-between px-3",
3800
+ style: {
3801
+ borderBottom: "1px solid var(--ide-border)",
3802
+ color: "var(--ide-text-tertiary)"
3803
+ },
3804
+ children: [
3805
+ /* @__PURE__ */ jsxDEV2("span", {
3806
+ className: "text-[10px] font-semibold uppercase tracking-wider",
3807
+ children: "Explorer"
3808
+ }, undefined, false, undefined, this),
3809
+ /* @__PURE__ */ jsxDEV2("div", {
3810
+ className: "flex gap-1",
3811
+ children: [
3812
+ /* @__PURE__ */ jsxDEV2("button", {
3813
+ className: "flex h-5 w-5 items-center justify-center rounded",
3814
+ style: { color: "var(--ide-text-tertiary)" },
3815
+ onClick: () => handleCreateFile("/"),
3816
+ title: "New File",
3817
+ children: /* @__PURE__ */ jsxDEV2("svg", {
3818
+ width: "12",
3819
+ height: "12",
3820
+ viewBox: "0 0 24 24",
3821
+ fill: "none",
3822
+ stroke: "currentColor",
3823
+ strokeWidth: "2",
3824
+ children: [
3825
+ /* @__PURE__ */ jsxDEV2("path", {
3826
+ d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
3827
+ }, undefined, false, undefined, this),
3828
+ /* @__PURE__ */ jsxDEV2("polyline", {
3829
+ points: "14 2 14 8 20 8"
3830
+ }, undefined, false, undefined, this),
3831
+ /* @__PURE__ */ jsxDEV2("line", {
3832
+ x1: "12",
3833
+ y1: "18",
3834
+ x2: "12",
3835
+ y2: "12"
3836
+ }, undefined, false, undefined, this),
3837
+ /* @__PURE__ */ jsxDEV2("line", {
3838
+ x1: "9",
3839
+ y1: "15",
3840
+ x2: "15",
3841
+ y2: "15"
3842
+ }, undefined, false, undefined, this)
3843
+ ]
3844
+ }, undefined, true, undefined, this)
3845
+ }, undefined, false, undefined, this),
3846
+ /* @__PURE__ */ jsxDEV2("button", {
3847
+ className: "flex h-5 w-5 items-center justify-center rounded",
3848
+ style: { color: "var(--ide-text-tertiary)" },
3849
+ onClick: () => handleCreateDir("/"),
3850
+ title: "New Folder",
3851
+ children: /* @__PURE__ */ jsxDEV2("svg", {
3852
+ width: "12",
3853
+ height: "12",
3854
+ viewBox: "0 0 24 24",
3855
+ fill: "none",
3856
+ stroke: "currentColor",
3857
+ strokeWidth: "2",
3858
+ children: [
3859
+ /* @__PURE__ */ jsxDEV2("path", {
3860
+ d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
3861
+ }, undefined, false, undefined, this),
3862
+ /* @__PURE__ */ jsxDEV2("line", {
3863
+ x1: "12",
3864
+ y1: "11",
3865
+ x2: "12",
3866
+ y2: "17"
3867
+ }, undefined, false, undefined, this),
3868
+ /* @__PURE__ */ jsxDEV2("line", {
3869
+ x1: "9",
3870
+ y1: "14",
3871
+ x2: "15",
3872
+ y2: "14"
3873
+ }, undefined, false, undefined, this)
3874
+ ]
3875
+ }, undefined, true, undefined, this)
3876
+ }, undefined, false, undefined, this),
3877
+ /* @__PURE__ */ jsxDEV2("button", {
3878
+ className: "flex h-5 w-5 items-center justify-center rounded",
3879
+ style: { color: "var(--ide-text-tertiary)" },
3880
+ onClick: loadTree,
3881
+ title: "Refresh",
3882
+ children: /* @__PURE__ */ jsxDEV2("svg", {
3883
+ width: "10",
3884
+ height: "10",
3885
+ viewBox: "0 0 24 24",
3886
+ fill: "none",
3887
+ stroke: "currentColor",
3888
+ strokeWidth: "2.5",
3889
+ children: [
3890
+ /* @__PURE__ */ jsxDEV2("path", {
3891
+ d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
3892
+ }, undefined, false, undefined, this),
3893
+ /* @__PURE__ */ jsxDEV2("path", {
3894
+ d: "M3 3v5h5"
3895
+ }, undefined, false, undefined, this)
3896
+ ]
3897
+ }, undefined, true, undefined, this)
3898
+ }, undefined, false, undefined, this)
3899
+ ]
3900
+ }, undefined, true, undefined, this)
3901
+ ]
3902
+ }, undefined, true, undefined, this),
3903
+ /* @__PURE__ */ jsxDEV2("div", {
3904
+ className: "flex-1 overflow-y-auto ide-scrollbar py-1",
3905
+ children: [
3906
+ tree.map((entry) => /* @__PURE__ */ jsxDEV2(TreeNode, {
3907
+ entry,
3908
+ depth: 0,
3909
+ expandedDirs,
3910
+ selectedFile,
3911
+ onToggleDir: handleToggleDir,
3912
+ onSelectFile: handleSelectFile,
3913
+ onContextMenu: handleContextMenu
3914
+ }, entry.path, false, undefined, this)),
3915
+ inlineAction && /* @__PURE__ */ jsxDEV2(InlineInput, {
3916
+ defaultValue: inlineAction.currentName ?? "",
3917
+ placeholder: inlineAction.type === "create-file" ? "filename.ts" : inlineAction.type === "create-dir" ? "folder-name" : "new name",
3918
+ onSubmit: handleInlineSubmit,
3919
+ onCancel: () => setInlineAction(null)
3920
+ }, undefined, false, undefined, this)
3921
+ ]
3922
+ }, undefined, true, undefined, this),
3923
+ contextMenu && /* @__PURE__ */ jsxDEV2("div", {
3924
+ className: "fixed z-50 min-w-[140px] rounded-md py-1",
3925
+ style: {
3926
+ left: contextMenu.x,
3927
+ top: contextMenu.y,
3928
+ background: "var(--ide-surface)",
3929
+ border: "1px solid var(--ide-border)",
3930
+ boxShadow: "0 4px 12px rgba(0,0,0,0.4)"
3931
+ },
3932
+ children: [
3933
+ /* @__PURE__ */ jsxDEV2("button", {
3934
+ className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-[var(--ide-surface-hover)]",
3935
+ style: { color: "var(--ide-text-secondary)" },
3936
+ onClick: () => handleCreateFile(contextMenu.parentPath),
3937
+ children: "New File"
3938
+ }, undefined, false, undefined, this),
3939
+ /* @__PURE__ */ jsxDEV2("button", {
3940
+ className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-[var(--ide-surface-hover)]",
3941
+ style: { color: "var(--ide-text-secondary)" },
3942
+ onClick: () => handleCreateDir(contextMenu.parentPath),
3943
+ children: "New Folder"
3944
+ }, undefined, false, undefined, this),
3945
+ contextMenu.entry && /* @__PURE__ */ jsxDEV2(Fragment, {
3946
+ children: [
3947
+ /* @__PURE__ */ jsxDEV2("div", {
3948
+ className: "mx-2 my-1",
3949
+ style: { borderTop: "1px solid var(--ide-border)" }
3950
+ }, undefined, false, undefined, this),
3951
+ /* @__PURE__ */ jsxDEV2("button", {
3952
+ className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-[var(--ide-surface-hover)]",
3953
+ style: { color: "var(--ide-text-secondary)" },
3954
+ onClick: () => handleRename(contextMenu.entry),
3955
+ children: "Rename"
3956
+ }, undefined, false, undefined, this),
3957
+ /* @__PURE__ */ jsxDEV2("button", {
3958
+ className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-[var(--ide-surface-hover)]",
3959
+ style: { color: "var(--ide-error)" },
3960
+ onClick: () => handleDelete(contextMenu.entry),
3961
+ children: "Delete"
3962
+ }, undefined, false, undefined, this)
3963
+ ]
3964
+ }, undefined, true, undefined, this)
3965
+ ]
3966
+ }, undefined, true, undefined, this)
3967
+ ]
3968
+ }, undefined, true, undefined, this);
3969
+ }
3970
+ var EXTENSION_ICONS;
3971
+ var init_file_explorer = __esm(() => {
3972
+ init_filesystem();
3973
+ init_path_policy();
3974
+ init_use_vibe_ide();
3975
+ EXTENSION_ICONS = {
3976
+ ts: "TS",
3977
+ tsx: "TX",
3978
+ js: "JS",
3979
+ jsx: "JX",
3980
+ json: "{}",
3981
+ css: "#",
3982
+ scss: "#",
3983
+ html: "<>",
3984
+ md: "MD",
3985
+ svg: "SV",
3986
+ png: "IM",
3987
+ jpg: "IM",
3988
+ gif: "IM",
3989
+ vue: "VU",
3990
+ yaml: "YM",
3991
+ yml: "YM",
3992
+ toml: "TM",
3993
+ lock: "LK",
3994
+ txt: "TX"
3995
+ };
3996
+ });
3997
+
3998
+ // src/react/code-editor.tsx
3999
+ var exports_code_editor = {};
4000
+ __export(exports_code_editor, {
4001
+ CodeEditor: () => CodeEditor
4002
+ });
4003
+ import {
4004
+ useCallback as useCallback4,
4005
+ useEffect as useEffect4,
4006
+ useRef as useRef4,
4007
+ useState as useState4
4008
+ } from "react";
4009
+ import Editor, { loader } from "@monaco-editor/react";
4010
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
4011
+ function getLanguage(filename) {
4012
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
4013
+ return EXTENSION_TO_LANGUAGE[ext] ?? "plaintext";
4014
+ }
4015
+ function getBasename2(path) {
4016
+ return path.split("/").pop() ?? path;
4017
+ }
4018
+ function CodeEditor({
4019
+ initialFile = null,
4020
+ onActiveFileChange
4021
+ }) {
4022
+ const { webcontainer } = useVibeIde();
4023
+ const instance2 = webcontainer.instance;
4024
+ const coalescer = webcontainer.coalescer;
4025
+ const [tabs, setTabs] = useState4([]);
4026
+ const [activeTab, setActiveTab] = useState4(null);
4027
+ const [editorReady, setEditorReady] = useState4(false);
4028
+ const editorRef = useRef4(null);
4029
+ const monacoRef = useRef4(null);
4030
+ const tabsRef = useRef4([]);
4031
+ const modelsRef = useRef4(new Map);
4032
+ const indicatorTimers = useRef4(new Map);
4033
+ useEffect4(() => {
4034
+ tabsRef.current = tabs;
4035
+ }, [tabs]);
4036
+ const openFile = useCallback4(async (path) => {
4037
+ if (!instance2 || !monacoRef.current)
4038
+ return;
4039
+ const existing = tabsRef.current.find((t) => t.path === path);
4040
+ if (existing) {
4041
+ setActiveTab(path);
4042
+ onActiveFileChange?.(path);
4043
+ return;
4044
+ }
4045
+ try {
4046
+ const content = await instance2.fs.readFile(path, "utf8");
4047
+ const byteSize = new TextEncoder().encode(content).byteLength;
4048
+ const isReadOnly = byteSize > LARGE_FILE_THRESHOLD;
4049
+ const language = getLanguage(getBasename2(path));
4050
+ const uri = monacoRef.current.Uri.parse(`file://${path}`);
4051
+ let model = monacoRef.current.editor.getModel(uri);
4052
+ if (!model) {
4053
+ model = monacoRef.current.editor.createModel(content, language, uri);
4054
+ modelsRef.current.set(path, model);
4055
+ } else {
4056
+ model.setValue(content);
4057
+ }
4058
+ const newTab = {
4059
+ path,
4060
+ name: getBasename2(path),
4061
+ isModified: false,
4062
+ isReadOnly,
4063
+ indicator: isReadOnly ? "Read-only (large file)" : undefined
4064
+ };
4065
+ setTabs((prev) => [...prev, newTab]);
4066
+ setActiveTab(path);
4067
+ onActiveFileChange?.(path);
4068
+ } catch (e) {
4069
+ console.error(`CodeEditor: failed to open ${path}`, e);
4070
+ }
4071
+ }, [instance2, onActiveFileChange]);
4072
+ useEffect4(() => {
4073
+ if (initialFile && editorReady && instance2) {
4074
+ openFile(initialFile);
4075
+ }
4076
+ }, [initialFile, editorReady, instance2, openFile]);
4077
+ const closeTab = useCallback4((path) => {
4078
+ const model = modelsRef.current.get(path);
4079
+ if (model) {
4080
+ model.dispose();
4081
+ modelsRef.current.delete(path);
4082
+ }
4083
+ const timer = indicatorTimers.current.get(path);
4084
+ if (timer) {
4085
+ window.clearTimeout(timer);
4086
+ indicatorTimers.current.delete(path);
4087
+ }
4088
+ setTabs((prev) => {
4089
+ const next = prev.filter((t) => t.path !== path);
4090
+ if (activeTab === path) {
4091
+ const newActive = next.length > 0 ? next[next.length - 1].path : null;
4092
+ queueMicrotask(() => {
4093
+ setActiveTab(newActive);
4094
+ onActiveFileChange?.(newActive);
4095
+ });
4096
+ }
4097
+ return next;
4098
+ });
4099
+ }, [activeTab, onActiveFileChange]);
4100
+ const handleEditorBeforeMount = useCallback4((m) => {
4101
+ monacoRef.current = m;
4102
+ m.languages.typescript.typescriptDefaults.setCompilerOptions({
4103
+ target: m.languages.typescript.ScriptTarget.ESNext,
4104
+ module: m.languages.typescript.ModuleKind.ESNext,
4105
+ moduleResolution: m.languages.typescript.ModuleResolutionKind.NodeJs,
4106
+ jsx: m.languages.typescript.JsxEmit.ReactJSX,
4107
+ esModuleInterop: true,
4108
+ allowJs: true,
4109
+ strict: true
4110
+ });
4111
+ m.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
4112
+ noSemanticValidation: true,
4113
+ noSyntaxValidation: false
4114
+ });
4115
+ }, []);
4116
+ const handleEditorMount = useCallback4((editor) => {
4117
+ editorRef.current = editor;
4118
+ setEditorReady(true);
4119
+ editor.onDidChangeModelContent(() => {
4120
+ const model = editor.getModel();
4121
+ if (!model)
4122
+ return;
4123
+ const path = model.uri.path;
4124
+ const currentTabs = tabsRef.current;
4125
+ const tab = currentTabs.find((t) => t.path === path);
4126
+ if (!tab)
4127
+ return;
4128
+ const content = model.getValue();
4129
+ setTabs((prev) => prev.map((t) => t.path === path ? { ...t, isModified: true } : t));
4130
+ if (tab.isReadOnly)
4131
+ return;
4132
+ if (coalescer) {
4133
+ coalescer.write(path, content);
4134
+ } else if (instance2) {
4135
+ instance2.fs.writeFile(path, content).catch((e) => {
4136
+ console.error(`CodeEditor: write failed for ${path}`, e);
4137
+ });
4138
+ }
4139
+ });
4140
+ }, [coalescer, instance2]);
4141
+ useEffect4(() => {
4142
+ if (!editorRef.current || !monacoRef.current)
4143
+ return;
4144
+ if (!activeTab) {
4145
+ editorRef.current.setModel(null);
4146
+ editorRef.current.updateOptions({ readOnly: true });
4147
+ return;
4148
+ }
4149
+ const uri = monacoRef.current.Uri.parse(`file://${activeTab}`);
4150
+ const model = monacoRef.current.editor.getModel(uri);
4151
+ if (model && editorRef.current.getModel() !== model) {
4152
+ editorRef.current.setModel(model);
4153
+ }
4154
+ const tab = tabs.find((t) => t.path === activeTab);
4155
+ editorRef.current.updateOptions({ readOnly: tab?.isReadOnly ?? false });
4156
+ }, [activeTab, tabs]);
4157
+ useEffect4(() => {
4158
+ if (!coalescer)
4159
+ return;
4160
+ const unsub = coalescer.onWrite((writtenPath) => {
4161
+ const model = modelsRef.current.get(writtenPath);
4162
+ if (!model || !instance2)
4163
+ return;
4164
+ const isActivelyEditing = activeTab === writtenPath && editorRef.current?.hasTextFocus();
4165
+ if (isActivelyEditing)
4166
+ return;
4167
+ instance2.fs.readFile(writtenPath, "utf8").then((newContent) => {
4168
+ if (model.getValue() !== newContent) {
4169
+ model.setValue(newContent);
4170
+ setTabs((prev) => prev.map((t) => t.path === writtenPath ? { ...t, indicator: "Agent modified", isModified: false } : t));
4171
+ const existing = indicatorTimers.current.get(writtenPath);
4172
+ if (existing)
4173
+ window.clearTimeout(existing);
4174
+ indicatorTimers.current.set(writtenPath, window.setTimeout(() => {
4175
+ setTabs((prev) => prev.map((t) => t.path === writtenPath ? { ...t, indicator: undefined } : t));
4176
+ indicatorTimers.current.delete(writtenPath);
4177
+ }, 3000));
4178
+ }
4179
+ }).catch(() => {});
4180
+ });
4181
+ return unsub;
4182
+ }, [coalescer, instance2, activeTab]);
4183
+ useEffect4(() => {
4184
+ if (!coalescer)
4185
+ return;
4186
+ const unsub = coalescer.onDelete((deletedPath) => {
4187
+ setTabs((prev) => {
4188
+ const toClose = prev.filter((t) => t.path === deletedPath || t.path.startsWith(deletedPath + "/"));
4189
+ if (toClose.length === 0)
4190
+ return prev;
4191
+ for (const tab of toClose) {
4192
+ const model = modelsRef.current.get(tab.path);
4193
+ if (model) {
4194
+ model.dispose();
4195
+ modelsRef.current.delete(tab.path);
4196
+ }
4197
+ const timer = indicatorTimers.current.get(tab.path);
4198
+ if (timer) {
4199
+ window.clearTimeout(timer);
4200
+ indicatorTimers.current.delete(tab.path);
4201
+ }
4202
+ }
4203
+ const closedPaths = new Set(toClose.map((t) => t.path));
4204
+ const remaining = prev.filter((t) => !closedPaths.has(t.path));
4205
+ if (activeTab && closedPaths.has(activeTab)) {
4206
+ const newActive = remaining.length > 0 ? remaining[remaining.length - 1].path : null;
4207
+ queueMicrotask(() => {
4208
+ setActiveTab(newActive);
4209
+ onActiveFileChange?.(newActive);
4210
+ });
4211
+ }
4212
+ return remaining;
4213
+ });
4214
+ });
4215
+ return unsub;
4216
+ }, [coalescer, activeTab, onActiveFileChange]);
4217
+ useEffect4(() => {
4218
+ return () => {
4219
+ for (const model of modelsRef.current.values()) {
4220
+ model.dispose();
4221
+ }
4222
+ modelsRef.current.clear();
4223
+ for (const timer of indicatorTimers.current.values()) {
4224
+ window.clearTimeout(timer);
4225
+ }
4226
+ indicatorTimers.current.clear();
4227
+ };
4228
+ }, []);
4229
+ return /* @__PURE__ */ jsxDEV3("div", {
4230
+ className: "flex h-full w-full flex-col",
4231
+ style: { background: "var(--ide-surface)" },
4232
+ children: [
4233
+ tabs.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
4234
+ className: "flex h-8 shrink-0 items-center gap-0 overflow-x-auto",
4235
+ style: {
4236
+ background: "var(--ide-bg)",
4237
+ borderBottom: "1px solid var(--ide-border)"
4238
+ },
4239
+ children: tabs.map((tab) => /* @__PURE__ */ jsxDEV3("div", {
4240
+ className: "group flex h-full shrink-0 items-center gap-1.5 px-3 text-xs cursor-pointer",
4241
+ style: {
4242
+ borderRight: "1px solid var(--ide-border)",
4243
+ background: activeTab === tab.path ? "var(--ide-surface)" : "transparent",
4244
+ color: activeTab === tab.path ? "var(--ide-text)" : "var(--ide-text-tertiary)"
4245
+ },
4246
+ onClick: () => {
4247
+ setActiveTab(tab.path);
4248
+ onActiveFileChange?.(tab.path);
4249
+ },
4250
+ children: [
4251
+ tab.isModified && /* @__PURE__ */ jsxDEV3("span", {
4252
+ className: "inline-block h-1.5 w-1.5 shrink-0 rounded-full",
4253
+ style: { background: "var(--ide-accent)" }
4254
+ }, undefined, false, undefined, this),
4255
+ /* @__PURE__ */ jsxDEV3("span", {
4256
+ className: "truncate max-w-[120px]",
4257
+ children: tab.name
4258
+ }, undefined, false, undefined, this),
4259
+ tab.indicator && /* @__PURE__ */ jsxDEV3("span", {
4260
+ className: "text-[9px] italic",
4261
+ style: { color: "var(--ide-accent-muted)" },
4262
+ children: tab.indicator
4263
+ }, undefined, false, undefined, this),
4264
+ /* @__PURE__ */ jsxDEV3("button", {
4265
+ className: "ml-1 flex h-4 w-4 shrink-0 items-center justify-center rounded opacity-0 group-hover:opacity-100",
4266
+ style: { color: "var(--ide-text-tertiary)" },
4267
+ onClick: (e) => {
4268
+ e.stopPropagation();
4269
+ closeTab(tab.path);
4270
+ },
4271
+ title: "Close",
4272
+ children: /* @__PURE__ */ jsxDEV3("svg", {
4273
+ width: "8",
4274
+ height: "8",
4275
+ viewBox: "0 0 8 8",
4276
+ stroke: "currentColor",
4277
+ strokeWidth: "1.5",
4278
+ children: [
4279
+ /* @__PURE__ */ jsxDEV3("line", {
4280
+ x1: "1",
4281
+ y1: "1",
4282
+ x2: "7",
4283
+ y2: "7"
4284
+ }, undefined, false, undefined, this),
4285
+ /* @__PURE__ */ jsxDEV3("line", {
4286
+ x1: "7",
4287
+ y1: "1",
4288
+ x2: "1",
4289
+ y2: "7"
4290
+ }, undefined, false, undefined, this)
4291
+ ]
4292
+ }, undefined, true, undefined, this)
4293
+ }, undefined, false, undefined, this)
4294
+ ]
4295
+ }, tab.path, true, undefined, this))
4296
+ }, undefined, false, undefined, this),
4297
+ /* @__PURE__ */ jsxDEV3("div", {
4298
+ className: "relative flex-1 overflow-hidden",
4299
+ children: [
4300
+ /* @__PURE__ */ jsxDEV3(Editor, {
4301
+ theme: "vs-dark",
4302
+ beforeMount: handleEditorBeforeMount,
4303
+ onMount: handleEditorMount,
4304
+ options: {
4305
+ fontSize: 13,
4306
+ fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace",
4307
+ fontLigatures: true,
4308
+ minimap: { enabled: false },
4309
+ scrollBeyondLastLine: false,
4310
+ smoothScrolling: true,
4311
+ cursorSmoothCaretAnimation: "on",
4312
+ renderWhitespace: "selection",
4313
+ bracketPairColorization: { enabled: true },
4314
+ automaticLayout: true,
4315
+ tabSize: 2,
4316
+ wordWrap: "on",
4317
+ padding: { top: 8 },
4318
+ lineNumbersMinChars: 3,
4319
+ glyphMargin: false,
4320
+ folding: true,
4321
+ links: true,
4322
+ contextmenu: true,
4323
+ quickSuggestions: true,
4324
+ suggestOnTriggerCharacters: true,
4325
+ acceptSuggestionOnCommitCharacter: true,
4326
+ wordBasedSuggestions: "currentDocument",
4327
+ readOnly: activeTab == null ? true : tabs.find((tab) => tab.path === activeTab)?.isReadOnly ?? false
4328
+ }
4329
+ }, undefined, false, undefined, this),
4330
+ tabs.length === 0 && /* @__PURE__ */ jsxDEV3("div", {
4331
+ className: "absolute inset-0 flex items-center justify-center",
4332
+ style: { background: "var(--ide-surface)" },
4333
+ children: /* @__PURE__ */ jsxDEV3("div", {
4334
+ className: "flex flex-col items-center justify-center gap-3",
4335
+ children: [
4336
+ /* @__PURE__ */ jsxDEV3("div", {
4337
+ className: "flex h-12 w-12 items-center justify-center rounded-xl",
4338
+ style: { background: "var(--ide-accent-soft)" },
4339
+ children: /* @__PURE__ */ jsxDEV3("svg", {
4340
+ width: "24",
4341
+ height: "24",
4342
+ viewBox: "0 0 24 24",
4343
+ fill: "none",
4344
+ stroke: "var(--ide-accent)",
4345
+ strokeWidth: "1.5",
4346
+ strokeLinecap: "round",
4347
+ strokeLinejoin: "round",
4348
+ children: [
4349
+ /* @__PURE__ */ jsxDEV3("polyline", {
4350
+ points: "16 18 22 12 16 6"
4351
+ }, undefined, false, undefined, this),
4352
+ /* @__PURE__ */ jsxDEV3("polyline", {
4353
+ points: "8 6 2 12 8 18"
4354
+ }, undefined, false, undefined, this)
4355
+ ]
4356
+ }, undefined, true, undefined, this)
4357
+ }, undefined, false, undefined, this),
4358
+ /* @__PURE__ */ jsxDEV3("span", {
4359
+ className: "text-sm",
4360
+ style: { color: "var(--ide-text-tertiary)" },
4361
+ children: "Select a file from the explorer to start editing"
4362
+ }, undefined, false, undefined, this)
4363
+ ]
4364
+ }, undefined, true, undefined, this)
4365
+ }, undefined, false, undefined, this)
4366
+ ]
4367
+ }, undefined, true, undefined, this)
4368
+ ]
4369
+ }, undefined, true, undefined, this);
4370
+ }
4371
+ var EXTENSION_TO_LANGUAGE;
4372
+ var init_code_editor = __esm(() => {
4373
+ init_use_vibe_ide();
4374
+ init_path_policy();
4375
+ loader.config({ paths: { vs: "/monaco-editor/min/vs" } });
4376
+ EXTENSION_TO_LANGUAGE = {
4377
+ ts: "typescript",
4378
+ tsx: "typescript",
4379
+ js: "javascript",
4380
+ jsx: "javascript",
4381
+ mjs: "javascript",
4382
+ cjs: "javascript",
4383
+ json: "json",
4384
+ css: "css",
4385
+ scss: "scss",
4386
+ less: "less",
4387
+ html: "html",
4388
+ htm: "html",
4389
+ xml: "xml",
4390
+ svg: "xml",
4391
+ md: "markdown",
4392
+ yaml: "yaml",
4393
+ yml: "yaml",
4394
+ toml: "ini",
4395
+ sh: "shell",
4396
+ bash: "shell",
4397
+ vue: "html",
4398
+ graphql: "graphql",
4399
+ gql: "graphql",
4400
+ sql: "sql",
4401
+ py: "python",
4402
+ rb: "ruby",
4403
+ go: "go",
4404
+ rs: "rust",
4405
+ java: "java",
4406
+ kt: "kotlin",
4407
+ swift: "swift",
4408
+ c: "c",
4409
+ cpp: "cpp",
4410
+ h: "cpp",
4411
+ cs: "csharp"
4412
+ };
4413
+ });
4414
+
4415
+ // src/react/terminal-view.tsx
4416
+ var exports_terminal_view = {};
4417
+ __export(exports_terminal_view, {
4418
+ TerminalView: () => TerminalView
4419
+ });
4420
+ import { useEffect as useEffect5, useRef as useRef5 } from "react";
4421
+ import { Terminal } from "@xterm/xterm";
4422
+ import"@xterm/xterm/css/xterm.css";
4423
+ import { FitAddon } from "@xterm/addon-fit";
4424
+ import { WebLinksAddon } from "@xterm/addon-web-links";
4425
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
4426
+ function TerminalView({ processRegistry }) {
4427
+ const containerRef = useRef5(null);
4428
+ const termRef = useRef5(null);
4429
+ const fitAddonRef = useRef5(null);
4430
+ const resizeObserverRef = useRef5(null);
4431
+ useEffect5(() => {
4432
+ if (!containerRef.current)
4433
+ return;
4434
+ const term = new Terminal({
4435
+ theme: TERMINAL_THEME,
4436
+ fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace",
4437
+ fontSize: 13,
4438
+ lineHeight: 1.4,
4439
+ scrollback: SCROLLBACK,
4440
+ cursorBlink: true,
4441
+ cursorStyle: "bar",
4442
+ allowProposedApi: true,
4443
+ convertEol: true,
4444
+ disableStdin: true
4445
+ });
4446
+ const fitAddon = new FitAddon;
4447
+ term.loadAddon(fitAddon);
4448
+ const webLinksAddon = new WebLinksAddon((_, uri) => {
4449
+ try {
4450
+ const url = new URL(uri);
4451
+ const isLocal = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "0.0.0.0" || url.origin === window.location.origin;
4452
+ if (isLocal) {
4453
+ window.open(uri, "_blank", "noopener,noreferrer");
4454
+ } else {
4455
+ const allow = window.confirm(`Open external link?
4456
+
4457
+ ${uri}
4458
+
4459
+ This link points to an external origin.`);
4460
+ if (allow) {
4461
+ window.open(uri, "_blank", "noopener,noreferrer");
4462
+ }
4463
+ }
4464
+ } catch {}
4465
+ });
4466
+ term.loadAddon(webLinksAddon);
4467
+ import("@xterm/addon-webgl").then(({ WebglAddon }) => {
4468
+ try {
4469
+ const webglAddon = new WebglAddon;
4470
+ webglAddon.onContextLoss(() => {
4471
+ webglAddon.dispose();
4472
+ });
4473
+ term.loadAddon(webglAddon);
4474
+ } catch {}
4475
+ }).catch(() => {});
4476
+ term.open(containerRef.current);
4477
+ requestAnimationFrame(() => {
4478
+ fitAddon.fit();
4479
+ });
4480
+ termRef.current = term;
4481
+ fitAddonRef.current = fitAddon;
4482
+ const observer = new ResizeObserver(() => {
4483
+ requestAnimationFrame(() => {
4484
+ try {
4485
+ fitAddon.fit();
4486
+ } catch {}
4487
+ });
4488
+ });
4489
+ observer.observe(containerRef.current);
4490
+ resizeObserverRef.current = observer;
4491
+ term.writeln("\x1B[90m--- Terminal ready ---\x1B[0m");
4492
+ return () => {
4493
+ observer.disconnect();
4494
+ term.dispose();
4495
+ termRef.current = null;
4496
+ fitAddonRef.current = null;
4497
+ };
4498
+ }, []);
4499
+ useEffect5(() => {
4500
+ if (!processRegistry || !termRef.current)
4501
+ return;
4502
+ const term = termRef.current;
4503
+ const unsub = processRegistry.onOutput((_key, chunk) => {
4504
+ term.write(chunk);
4505
+ });
4506
+ return unsub;
4507
+ }, [processRegistry]);
4508
+ return /* @__PURE__ */ jsxDEV4("div", {
4509
+ className: "h-full w-full overflow-hidden",
4510
+ style: { background: TERMINAL_THEME.background },
4511
+ children: /* @__PURE__ */ jsxDEV4("div", {
4512
+ ref: containerRef,
4513
+ className: "h-full w-full",
4514
+ style: { padding: "8px 4px 4px 8px" }
4515
+ }, undefined, false, undefined, this)
4516
+ }, undefined, false, undefined, this);
4517
+ }
4518
+ var SCROLLBACK = 5000, TERMINAL_THEME;
4519
+ var init_terminal_view = __esm(() => {
4520
+ TERMINAL_THEME = {
4521
+ background: "#18181b",
4522
+ foreground: "#fafafa",
4523
+ cursor: "#fafafa",
4524
+ cursorAccent: "#18181b",
4525
+ selectionBackground: "rgba(234, 179, 8, 0.2)",
4526
+ selectionForeground: "#fafafa",
4527
+ black: "#09090b",
4528
+ red: "#ef4444",
4529
+ green: "#22c55e",
4530
+ yellow: "#f59e0b",
4531
+ blue: "#3b82f6",
4532
+ magenta: "#a855f7",
4533
+ cyan: "#06b6d4",
4534
+ white: "#fafafa",
4535
+ brightBlack: "#71717a",
4536
+ brightRed: "#f87171",
4537
+ brightGreen: "#4ade80",
4538
+ brightYellow: "#fbbf24",
4539
+ brightBlue: "#60a5fa",
4540
+ brightMagenta: "#c084fc",
4541
+ brightCyan: "#22d3ee",
4542
+ brightWhite: "#ffffff"
4543
+ };
4544
+ });
4545
+
4546
+ // src/power-user.ts
4547
+ init_file_explorer();
4548
+ init_code_editor();
4549
+ init_terminal_view();
4550
+ export {
4551
+ TerminalView,
4552
+ FileExplorer,
4553
+ CodeEditor
4554
+ };