@_davideast/stitch-mcp 0.5.1 → 0.5.2

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 (56) hide show
  1. package/dist/chunk-1fbzpreg.js +137 -0
  2. package/dist/chunk-1fbzpreg.js.map +10 -0
  3. package/dist/chunk-4zqw9exz.js +370 -0
  4. package/dist/chunk-4zqw9exz.js.map +16 -0
  5. package/dist/chunk-9g1t32qk.js +273 -0
  6. package/dist/chunk-9g1t32qk.js.map +10 -0
  7. package/dist/chunk-aa5ptn4s.js +768 -0
  8. package/dist/chunk-aa5ptn4s.js.map +18 -0
  9. package/dist/chunk-b9nzx4rf.js +19 -0
  10. package/dist/chunk-b9nzx4rf.js.map +9 -0
  11. package/dist/chunk-c87d10w8.js +109 -0
  12. package/dist/chunk-c87d10w8.js.map +10 -0
  13. package/dist/chunk-d0ffvq20.js +19 -0
  14. package/dist/chunk-d0ffvq20.js.map +9 -0
  15. package/dist/chunk-d8nttd6z.js +683 -0
  16. package/dist/chunk-d8nttd6z.js.map +17 -0
  17. package/dist/chunk-e4q3v109.js +31464 -0
  18. package/dist/chunk-e4q3v109.js.map +245 -0
  19. package/dist/chunk-etcps0m5.js +1495 -0
  20. package/dist/chunk-etcps0m5.js.map +23 -0
  21. package/dist/chunk-f89ve6pt.js +256 -0
  22. package/dist/chunk-f89ve6pt.js.map +11 -0
  23. package/dist/chunk-gydabgsn.js +10 -0
  24. package/dist/chunk-gydabgsn.js.map +9 -0
  25. package/dist/chunk-m4fp3pqc.js +167 -0
  26. package/dist/chunk-m4fp3pqc.js.map +10 -0
  27. package/dist/chunk-nnep362g.js +124 -0
  28. package/dist/chunk-nnep362g.js.map +12 -0
  29. package/dist/chunk-pgqvwkcy.js +736 -0
  30. package/dist/chunk-pgqvwkcy.js.map +16 -0
  31. package/dist/chunk-q8cr9t2z.js +415 -0
  32. package/dist/chunk-q8cr9t2z.js.map +20 -0
  33. package/dist/chunk-s2d39pkr.js +19 -0
  34. package/dist/chunk-s2d39pkr.js.map +9 -0
  35. package/dist/chunk-ststnnry.js +44150 -0
  36. package/dist/chunk-ststnnry.js.map +237 -0
  37. package/dist/chunk-sv2nt87j.js +24 -0
  38. package/dist/chunk-sv2nt87j.js.map +9 -0
  39. package/dist/chunk-t2razx5k.js +69 -0
  40. package/dist/chunk-t2razx5k.js.map +10 -0
  41. package/dist/commands/doctor/command.js +10 -6
  42. package/dist/commands/doctor/command.js.map +4 -4
  43. package/dist/commands/doctor/spec.d.ts +6 -0
  44. package/dist/commands/init/command.js +17 -9
  45. package/dist/commands/init/command.js.map +4 -4
  46. package/dist/commands/init/spec.d.ts +6 -0
  47. package/dist/commands/proxy/command.js +2 -3
  48. package/dist/commands/proxy/command.js.map +3 -3
  49. package/dist/commands/screens/command.js +2 -2
  50. package/dist/commands/serve/command.js +3 -3
  51. package/dist/commands/site/command.js +1 -1
  52. package/dist/commands/snapshot/command.js +1 -1
  53. package/dist/commands/tool/command.js +1 -1
  54. package/dist/commands/view/command.js +1 -1
  55. package/dist/index.js +2 -2
  56. package/package.json +1 -1
@@ -0,0 +1,683 @@
1
+ import {
2
+ openUrl,
3
+ require_jsx_dev_runtime
4
+ } from "./chunk-krfqppg2.js";
5
+ import {
6
+ Box_default,
7
+ Text,
8
+ use_app_default,
9
+ use_input_default
10
+ } from "./chunk-7y4xzvkz.js";
11
+ import {
12
+ require_react
13
+ } from "./chunk-4jwmvjb4.js";
14
+ import {
15
+ copyJson,
16
+ copyText,
17
+ downloadAndCopyImage,
18
+ downloadAndCopyText
19
+ } from "./chunk-34w2wfyp.js";
20
+ import"./chunk-akd997ec.js";
21
+ import"./chunk-q6sv0243.js";
22
+ import"./chunk-4jygt4d6.js";
23
+ import"./chunk-tz7wnw4s.js";
24
+ import"./chunk-3sfn889r.js";
25
+ import {
26
+ __toESM
27
+ } from "./chunk-9wyra8hs.js";
28
+
29
+ // src/ui/InteractiveViewer.tsx
30
+ var import_react2 = __toESM(require_react(), 1);
31
+
32
+ // src/ui/JsonTree.tsx
33
+ var import_react = __toESM(require_react(), 1);
34
+
35
+ // src/ui/copy-behaviors/handlers.ts
36
+ var defaultCopyHandler = {
37
+ async copy(ctx) {
38
+ try {
39
+ await copyJson(ctx.value);
40
+ const preview = typeof ctx.value === "string" ? `"${ctx.value.slice(0, 50)}${ctx.value.length > 50 ? "..." : ""}"` : JSON.stringify(ctx.value).slice(0, 50);
41
+ return { success: true, message: `Copied: ${preview}` };
42
+ } catch (error) {
43
+ return { success: false, message: `Copy failed: ${error}` };
44
+ }
45
+ },
46
+ async copyExtended(ctx) {
47
+ try {
48
+ const obj = { [ctx.key]: ctx.value };
49
+ await copyJson(obj);
50
+ return { success: true, message: `Copied: { ${ctx.key}: ... }` };
51
+ } catch (error) {
52
+ return { success: false, message: `Copy failed: ${error}` };
53
+ }
54
+ }
55
+ };
56
+ var imageUrlCopyHandler = {
57
+ async copy(ctx) {
58
+ try {
59
+ if (typeof ctx.value !== "string") {
60
+ return { success: false, message: "Value is not a URL string" };
61
+ }
62
+ await copyText(ctx.value);
63
+ return { success: true, message: `Copied URL: ${ctx.value.slice(0, 60)}...` };
64
+ } catch (error) {
65
+ return { success: false, message: `Copy failed: ${error}` };
66
+ }
67
+ },
68
+ async copyExtended(ctx) {
69
+ try {
70
+ if (typeof ctx.value !== "string") {
71
+ return { success: false, message: "Value is not a URL string" };
72
+ }
73
+ ctx.onProgress?.("\uD83D\uDCF7 Downloading image...");
74
+ await downloadAndCopyImage(ctx.value);
75
+ return { success: true, message: "\uD83D\uDCF7 Image copied to clipboard!" };
76
+ } catch (error) {
77
+ return { success: false, message: `Image copy failed: ${error}` };
78
+ }
79
+ }
80
+ };
81
+ var htmlCodeCopyHandler = {
82
+ async copy(ctx) {
83
+ try {
84
+ if (typeof ctx.value !== "string") {
85
+ return { success: false, message: "Value is not a URL string" };
86
+ }
87
+ await copyText(ctx.value);
88
+ return { success: true, message: `Copied URL: ${ctx.value.slice(0, 60)}...` };
89
+ } catch (error) {
90
+ return { success: false, message: `Copy failed: ${error}` };
91
+ }
92
+ },
93
+ async copyExtended(ctx) {
94
+ try {
95
+ if (typeof ctx.value !== "string") {
96
+ return { success: false, message: "Value is not a URL string" };
97
+ }
98
+ ctx.onProgress?.("\uD83D\uDCDD Downloading HTML code...");
99
+ await downloadAndCopyText(ctx.value);
100
+ return { success: true, message: "\uD83D\uDCDD HTML code copied to clipboard!" };
101
+ } catch (error) {
102
+ return { success: false, message: `HTML copy failed: ${error}` };
103
+ }
104
+ }
105
+ };
106
+
107
+ // src/ui/copy-behaviors/registry.ts
108
+ var registrations = [];
109
+ function registerHandler(matcher, handler) {
110
+ registrations.push({ matcher, handler });
111
+ }
112
+ function getHandler(path) {
113
+ for (let i = registrations.length - 1;i >= 0; i--) {
114
+ const registration = registrations[i];
115
+ if (registration && registration.matcher(path)) {
116
+ return registration.handler;
117
+ }
118
+ }
119
+ return defaultCopyHandler;
120
+ }
121
+ function endsWith(suffix) {
122
+ return (path) => path.endsWith(suffix);
123
+ }
124
+ registerHandler(endsWith(".thumbnailScreenshot.downloadUrl"), imageUrlCopyHandler);
125
+ registerHandler(endsWith(".screenshot.downloadUrl"), imageUrlCopyHandler);
126
+ registerHandler(endsWith(".htmlCode.downloadUrl"), htmlCodeCopyHandler);
127
+
128
+ // src/ui/navigation-behaviors/handler.ts
129
+ function screenInstanceNavigationHandler(ctx) {
130
+ if (!ctx.path.includes("screenInstances")) {
131
+ return { shouldNavigate: false };
132
+ }
133
+ if (ctx.value && typeof ctx.value === "object" && ctx.value.sourceScreen) {
134
+ return {
135
+ shouldNavigate: true,
136
+ target: ctx.value.sourceScreen,
137
+ type: "screen"
138
+ };
139
+ }
140
+ if (ctx.key === "sourceScreen" && typeof ctx.value === "string") {
141
+ return {
142
+ shouldNavigate: true,
143
+ target: ctx.value,
144
+ type: "screen"
145
+ };
146
+ }
147
+ return { shouldNavigate: false };
148
+ }
149
+ var navigationHandlers = [
150
+ screenInstanceNavigationHandler
151
+ ];
152
+ function getNavigationTarget(ctx) {
153
+ for (const handler of navigationHandlers) {
154
+ const result = handler(ctx);
155
+ if (result.shouldNavigate) {
156
+ return result;
157
+ }
158
+ }
159
+ return { shouldNavigate: false };
160
+ }
161
+
162
+ // src/ui/serve-behaviors/server.ts
163
+ import { createServer } from "node:http";
164
+ import { randomBytes } from "node:crypto";
165
+ async function serveHtmlInMemory(html, options) {
166
+ const timeout = options?.timeout ?? 5 * 60 * 1000;
167
+ const openBrowser = options?.openBrowser ?? true;
168
+ return new Promise((resolve, reject) => {
169
+ const server = createServer((req, res) => {
170
+ const nonce = randomBytes(16).toString("base64");
171
+ const csp = [
172
+ "default-src 'self' data: https:;",
173
+ `script-src 'self' 'nonce-${nonce}';`,
174
+ "style-src 'self' 'unsafe-inline';",
175
+ "object-src 'none';",
176
+ "base-uri 'self';"
177
+ ].join(" ");
178
+ res.writeHead(200, {
179
+ "Content-Type": "text/html; charset=utf-8",
180
+ "Content-Security-Policy": csp,
181
+ "X-Content-Type-Options": "nosniff",
182
+ "Referrer-Policy": "no-referrer"
183
+ });
184
+ const htmlWithNonces = html.replace(/<script(\b[^>]*)>/gi, `<script$1 nonce="${nonce}">`);
185
+ res.end(htmlWithNonces);
186
+ });
187
+ server.listen(0, "127.0.0.1", () => {
188
+ const address = server.address();
189
+ if (!address || typeof address === "string") {
190
+ reject(new Error("Failed to get server address"));
191
+ return;
192
+ }
193
+ const url = `http://127.0.0.1:${address.port}`;
194
+ const timer = setTimeout(() => server.close(), timeout);
195
+ const stop = () => {
196
+ clearTimeout(timer);
197
+ server.close();
198
+ };
199
+ if (openBrowser) {
200
+ openUrl(url);
201
+ }
202
+ resolve({ url, stop });
203
+ });
204
+ server.on("error", reject);
205
+ });
206
+ }
207
+
208
+ // src/ui/serve-behaviors/handlers.ts
209
+ var deps = {
210
+ serveHtmlInMemory
211
+ };
212
+ var htmlCodeServeHandler = {
213
+ async serve(ctx) {
214
+ try {
215
+ let url;
216
+ if (typeof ctx.value === "string") {
217
+ url = ctx.value;
218
+ } else if (typeof ctx.value === "object" && ctx.value?.downloadUrl) {
219
+ url = ctx.value.downloadUrl;
220
+ } else {
221
+ return { success: false, message: "No download URL found" };
222
+ }
223
+ ctx.onProgress?.("\uD83D\uDCE5 Downloading HTML...");
224
+ const response = await fetch(url);
225
+ if (!response.ok) {
226
+ return { success: false, message: `Download failed: ${response.status} ${response.statusText}` };
227
+ }
228
+ const html = await response.text();
229
+ ctx.onProgress?.("\uD83D\uDE80 Starting local server...");
230
+ const { url: serveUrl } = await deps.serveHtmlInMemory(html);
231
+ ctx.onProgress?.("\uD83C\uDF10 Opening browser...");
232
+ return { success: true, message: `\uD83C\uDF10 Preview at ${serveUrl} (auto-closes in 5 min)`, url: serveUrl };
233
+ } catch (error) {
234
+ return { success: false, message: `Serve failed: ${error instanceof Error ? error.message : String(error)}` };
235
+ }
236
+ }
237
+ };
238
+
239
+ // src/ui/serve-behaviors/registry.ts
240
+ var registrations2 = [];
241
+ function registerServeHandler(matcher, handler) {
242
+ registrations2.push({ matcher, handler });
243
+ }
244
+ function getServeHandler(path) {
245
+ for (let i = registrations2.length - 1;i >= 0; i--) {
246
+ const reg = registrations2[i];
247
+ if (reg && reg.matcher(path))
248
+ return reg.handler;
249
+ }
250
+ return null;
251
+ }
252
+ function endsWith2(suffix) {
253
+ return (path) => path.endsWith(suffix);
254
+ }
255
+ registerServeHandler(endsWith2(".htmlCode"), htmlCodeServeHandler);
256
+ registerServeHandler(endsWith2(".htmlCode.downloadUrl"), htmlCodeServeHandler);
257
+
258
+ // src/ui/JsonTree.tsx
259
+ var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
260
+ function getType(value) {
261
+ if (value === null)
262
+ return "null";
263
+ if (Array.isArray(value))
264
+ return "array";
265
+ return typeof value;
266
+ }
267
+ function buildVisibleTree(data, expandedIds, prefix = "", depth = 0, rootLabel) {
268
+ const nodes = [];
269
+ const type = getType(data);
270
+ if (rootLabel && prefix === "" && depth === 0) {
271
+ const isExpanded = expandedIds.has(rootLabel);
272
+ nodes.push({
273
+ id: rootLabel,
274
+ key: rootLabel,
275
+ value: data,
276
+ depth: 0,
277
+ isLeaf: false,
278
+ isExpanded,
279
+ hasChildren: true
280
+ });
281
+ if (isExpanded) {
282
+ nodes.push(...buildVisibleTree(data, expandedIds, rootLabel, 1));
283
+ }
284
+ return nodes;
285
+ }
286
+ if (type === "object" || type === "array") {
287
+ const keys = Object.keys(data);
288
+ for (const key of keys) {
289
+ const value = data[key];
290
+ const id = prefix ? `${prefix}.${key}` : key;
291
+ const valueType = getType(value);
292
+ const isLeaf = valueType !== "object" && valueType !== "array";
293
+ const hasChildren = !isLeaf && Object.keys(value).length > 0;
294
+ const isExpanded = expandedIds.has(id);
295
+ nodes.push({
296
+ id,
297
+ key,
298
+ value,
299
+ depth,
300
+ isLeaf,
301
+ isExpanded,
302
+ hasChildren
303
+ });
304
+ if (hasChildren && isExpanded) {
305
+ nodes.push(...buildVisibleTree(value, expandedIds, id, depth + 1));
306
+ }
307
+ }
308
+ }
309
+ return nodes;
310
+ }
311
+ var JsonTree = ({ data, rootLabel, onNavigate, onBack }) => {
312
+ const [expandedIds, setExpandedIds] = import_react.useState(() => {
313
+ const ids = new Set;
314
+ if (rootLabel) {
315
+ ids.add(rootLabel);
316
+ } else if (data && typeof data === "object") {
317
+ Object.keys(data).forEach((key) => ids.add(key));
318
+ }
319
+ return ids;
320
+ });
321
+ const [selectedIndex, setSelectedIndex] = import_react.useState(0);
322
+ const [feedbackMessage, setFeedbackMessage] = import_react.useState(null);
323
+ const lastCPressTime = import_react.useRef(0);
324
+ const feedbackTimeout = import_react.useRef(null);
325
+ const visibleNodes = import_react.useMemo(() => {
326
+ return buildVisibleTree(data, expandedIds, "", 0, rootLabel);
327
+ }, [data, expandedIds, rootLabel]);
328
+ const { exit } = use_app_default();
329
+ use_input_default((input, key) => {
330
+ if (input === "q") {
331
+ exit();
332
+ }
333
+ if (input === "c") {
334
+ const node = visibleNodes[selectedIndex];
335
+ if (!node)
336
+ return;
337
+ const now = Date.now();
338
+ const timeSinceLastC = now - lastCPressTime.current;
339
+ lastCPressTime.current = now;
340
+ const isDoubleTap = timeSinceLastC < 300;
341
+ const handler = getHandler(node.id);
342
+ const onProgress = (message) => {
343
+ if (feedbackTimeout.current)
344
+ clearTimeout(feedbackTimeout.current);
345
+ setFeedbackMessage(message);
346
+ };
347
+ const ctx = { key: node.key, value: node.value, path: node.id, onProgress };
348
+ const showFeedback = (result) => {
349
+ if (feedbackTimeout.current)
350
+ clearTimeout(feedbackTimeout.current);
351
+ setFeedbackMessage(result.message);
352
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 3000);
353
+ };
354
+ if (isDoubleTap) {
355
+ handler.copyExtended(ctx).then(showFeedback);
356
+ } else {
357
+ setTimeout(() => {
358
+ if (Date.now() - lastCPressTime.current >= 280) {
359
+ handler.copy(ctx).then(showFeedback);
360
+ }
361
+ }, 300);
362
+ }
363
+ return;
364
+ }
365
+ if (input === "s") {
366
+ const node = visibleNodes[selectedIndex];
367
+ if (!node)
368
+ return;
369
+ const handler = getServeHandler(node.id);
370
+ if (!handler) {
371
+ if (feedbackTimeout.current)
372
+ clearTimeout(feedbackTimeout.current);
373
+ setFeedbackMessage("⚠️ No preview available for this path");
374
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 3000);
375
+ return;
376
+ }
377
+ const onProgress = (message) => {
378
+ if (feedbackTimeout.current)
379
+ clearTimeout(feedbackTimeout.current);
380
+ setFeedbackMessage(message);
381
+ };
382
+ const ctx = { key: node.key, value: node.value, path: node.id, onProgress };
383
+ handler.serve(ctx).then((result) => {
384
+ if (feedbackTimeout.current)
385
+ clearTimeout(feedbackTimeout.current);
386
+ setFeedbackMessage(result.message);
387
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 1e4);
388
+ });
389
+ return;
390
+ }
391
+ if (input === "o") {
392
+ const node = visibleNodes[selectedIndex];
393
+ if (!node)
394
+ return;
395
+ let projectId;
396
+ const projectsMatch = node.id.match(/projects\.(\d+)/);
397
+ if (projectsMatch && projectsMatch[1]) {
398
+ const projectIndex = parseInt(projectsMatch[1], 10);
399
+ const project = data.projects?.[projectIndex];
400
+ if (project?.name) {
401
+ projectId = project.name.replace("projects/", "");
402
+ }
403
+ }
404
+ if (!projectId && typeof node.value === "object" && node.value?.name) {
405
+ const nameMatch = node.value.name.match(/projects\/(\d+)/);
406
+ if (nameMatch) {
407
+ projectId = nameMatch[1];
408
+ }
409
+ }
410
+ if (!projectId && node.key === "name" && typeof node.value === "string") {
411
+ const nameMatch = node.value.match(/projects\/(\d+)/);
412
+ if (nameMatch) {
413
+ projectId = nameMatch[1];
414
+ }
415
+ }
416
+ if (!projectId && (rootLabel === "screen" || rootLabel === "resource")) {
417
+ const nameMatch = data.name?.match(/projects\/(\d+)/);
418
+ if (nameMatch) {
419
+ projectId = nameMatch[1];
420
+ }
421
+ }
422
+ if (projectId) {
423
+ const url = `https://stitch.withgoogle.com/projects/${projectId}`;
424
+ openUrl(url);
425
+ if (feedbackTimeout.current)
426
+ clearTimeout(feedbackTimeout.current);
427
+ setFeedbackMessage(`\uD83D\uDD17 Opened project in browser`);
428
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 3000);
429
+ } else {
430
+ if (feedbackTimeout.current)
431
+ clearTimeout(feedbackTimeout.current);
432
+ setFeedbackMessage(`⚠️ No project found at this path`);
433
+ feedbackTimeout.current = setTimeout(() => setFeedbackMessage(null), 3000);
434
+ }
435
+ return;
436
+ }
437
+ if (key.upArrow) {
438
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
439
+ }
440
+ if (key.downArrow) {
441
+ setSelectedIndex(Math.min(visibleNodes.length - 1, selectedIndex + 1));
442
+ }
443
+ if (key.rightArrow || key.return) {
444
+ const node = visibleNodes[selectedIndex];
445
+ if (node && node.hasChildren) {
446
+ if (!node.isExpanded) {
447
+ const newExpanded = new Set(expandedIds);
448
+ newExpanded.add(node.id);
449
+ setExpandedIds(newExpanded);
450
+ }
451
+ }
452
+ if (key.return && node && onNavigate) {
453
+ const navResult = getNavigationTarget({ path: node.id, value: node.value, key: node.key });
454
+ if (navResult.shouldNavigate) {
455
+ onNavigate(navResult);
456
+ return;
457
+ }
458
+ }
459
+ }
460
+ if ((key.delete || key.backspace) && onBack) {
461
+ onBack();
462
+ return;
463
+ }
464
+ if (key.leftArrow) {
465
+ const node = visibleNodes[selectedIndex];
466
+ if (node) {
467
+ if (node.isExpanded) {
468
+ const newExpanded = new Set(expandedIds);
469
+ newExpanded.delete(node.id);
470
+ setExpandedIds(newExpanded);
471
+ } else {
472
+ const lastDot = node.id.lastIndexOf(".");
473
+ if (lastDot !== -1) {
474
+ const parentId = node.id.substring(0, lastDot);
475
+ const parentIndex = visibleNodes.findIndex((n) => n.id === parentId);
476
+ if (parentIndex !== -1) {
477
+ setSelectedIndex(parentIndex);
478
+ }
479
+ } else {}
480
+ }
481
+ }
482
+ }
483
+ });
484
+ const viewportHeight = 20;
485
+ const startRow = Math.max(0, Math.min(selectedIndex - 2, visibleNodes.length - viewportHeight));
486
+ const endRow = Math.min(startRow + viewportHeight, visibleNodes.length);
487
+ const viewportNodes = visibleNodes.slice(startRow, endRow);
488
+ if (!data || typeof data !== "object") {
489
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
490
+ children: [
491
+ "Invalid data: ",
492
+ String(data)
493
+ ]
494
+ }, undefined, true, undefined, this);
495
+ }
496
+ if (Object.keys(data).length === 0) {
497
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
498
+ children: "Empty object"
499
+ }, undefined, false, undefined, this);
500
+ }
501
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
502
+ flexDirection: "column",
503
+ children: [
504
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
505
+ color: "blue",
506
+ bold: true,
507
+ children: "JSON Viewer (Use Arrows to Navigate, 'q' to Quit)"
508
+ }, undefined, false, undefined, this),
509
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
510
+ flexDirection: "column",
511
+ borderStyle: "single",
512
+ children: [
513
+ viewportNodes.map((node, index) => {
514
+ const absoluteIndex = startRow + index;
515
+ const isSelected = absoluteIndex === selectedIndex;
516
+ const indentation = " ".repeat(node.depth);
517
+ let prefixChar = " ";
518
+ if (node.hasChildren) {
519
+ prefixChar = node.isExpanded ? "▼" : "▶";
520
+ }
521
+ let valueDisplay = "";
522
+ if (node.isLeaf) {
523
+ const valType = getType(node.value);
524
+ if (valType === "string")
525
+ valueDisplay = `"${node.value}"`;
526
+ else
527
+ valueDisplay = String(node.value);
528
+ } else {
529
+ const type = Array.isArray(node.value) ? "[]" : "{}";
530
+ const itemCount = Object.keys(node.value).length;
531
+ const label = node.value.title || node.value.name || node.value.displayName || node.value.id || null;
532
+ if (label && typeof label === "string") {
533
+ valueDisplay = `${type} "${label}" (${itemCount})`;
534
+ } else {
535
+ valueDisplay = `${type} ${itemCount} items`;
536
+ }
537
+ }
538
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
539
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
540
+ backgroundColor: isSelected ? "blue" : undefined,
541
+ color: isSelected ? "white" : undefined,
542
+ wrap: "truncate",
543
+ children: [
544
+ indentation,
545
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
546
+ color: "green",
547
+ children: [
548
+ prefixChar,
549
+ " ",
550
+ node.key
551
+ ]
552
+ }, undefined, true, undefined, this),
553
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
554
+ children: ": "
555
+ }, undefined, false, undefined, this),
556
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
557
+ color: "yellow",
558
+ children: valueDisplay
559
+ }, undefined, false, undefined, this)
560
+ ]
561
+ }, undefined, true, undefined, this)
562
+ }, node.id, false, undefined, this);
563
+ }),
564
+ visibleNodes.length > viewportHeight && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
565
+ color: "gray",
566
+ children: [
567
+ "... ",
568
+ visibleNodes.length - endRow,
569
+ " more items ..."
570
+ ]
571
+ }, undefined, true, undefined, this)
572
+ ]
573
+ }, undefined, true, undefined, this),
574
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
575
+ color: "gray",
576
+ children: [
577
+ "Selected Path: ",
578
+ visibleNodes[selectedIndex]?.id || "none",
579
+ " | 'c' copy, 'cc' extended, 's' preview"
580
+ ]
581
+ }, undefined, true, undefined, this),
582
+ feedbackMessage && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
583
+ color: "cyan",
584
+ bold: true,
585
+ children: feedbackMessage
586
+ }, undefined, false, undefined, this)
587
+ ]
588
+ }, undefined, true, undefined, this);
589
+ };
590
+
591
+ // src/ui/InteractiveViewer.tsx
592
+ var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
593
+ var InteractiveViewer = ({
594
+ initialData,
595
+ initialRootLabel,
596
+ initialHistory,
597
+ onFetch,
598
+ onExit
599
+ }) => {
600
+ const [history, setHistory] = import_react2.useState(() => {
601
+ if (initialHistory && initialHistory.length > 0) {
602
+ return [...initialHistory, { data: initialData, rootLabel: initialRootLabel }];
603
+ }
604
+ return [{ data: initialData, rootLabel: initialRootLabel }];
605
+ });
606
+ const [isLoading, setIsLoading] = import_react2.useState(false);
607
+ const [error, setError] = import_react2.useState(null);
608
+ const currentState = history[history.length - 1];
609
+ const handleNavigate = import_react2.useCallback(async (result) => {
610
+ if (!result.target)
611
+ return;
612
+ setIsLoading(true);
613
+ setError(null);
614
+ try {
615
+ const data = await onFetch(result.target);
616
+ let rootLabel;
617
+ if (result.type === "screen") {
618
+ rootLabel = "screen";
619
+ } else if (result.type === "project") {
620
+ rootLabel = "project";
621
+ } else {
622
+ rootLabel = "resource";
623
+ }
624
+ setHistory((prev) => [...prev, { data, rootLabel, resourcePath: result.target }]);
625
+ } catch (err) {
626
+ setError(`Navigation failed: ${err}`);
627
+ } finally {
628
+ setIsLoading(false);
629
+ }
630
+ }, [onFetch]);
631
+ const handleBack = import_react2.useCallback(() => {
632
+ if (history.length > 1) {
633
+ setHistory((prev) => prev.slice(0, -1));
634
+ setError(null);
635
+ }
636
+ }, [history.length]);
637
+ if (isLoading) {
638
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
639
+ flexDirection: "column",
640
+ children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
641
+ color: "blue",
642
+ children: "Loading..."
643
+ }, undefined, false, undefined, this)
644
+ }, undefined, false, undefined, this);
645
+ }
646
+ if (!currentState) {
647
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
648
+ color: "red",
649
+ children: "No data to display"
650
+ }, undefined, false, undefined, this);
651
+ }
652
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
653
+ flexDirection: "column",
654
+ children: [
655
+ history.length > 1 && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
656
+ color: "gray",
657
+ dimColor: true,
658
+ children: [
659
+ "← Press Backspace to go back (",
660
+ history.length - 1,
661
+ " level",
662
+ history.length > 2 ? "s" : "",
663
+ " deep)"
664
+ ]
665
+ }, undefined, true, undefined, this),
666
+ error && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
667
+ color: "red",
668
+ children: error
669
+ }, undefined, false, undefined, this),
670
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(JsonTree, {
671
+ data: currentState.data,
672
+ rootLabel: currentState.rootLabel,
673
+ onNavigate: handleNavigate,
674
+ onBack: history.length > 1 ? handleBack : undefined
675
+ }, history.length, false, undefined, this)
676
+ ]
677
+ }, undefined, true, undefined, this);
678
+ };
679
+ export {
680
+ InteractiveViewer
681
+ };
682
+
683
+ //# debugId=104B3994C661F7A564756E2164756E21