@mindstudio-ai/local-model-tunnel 0.5.7 → 0.5.8

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.
@@ -1,8 +1,17 @@
1
1
  import {
2
2
  TunnelRunner,
3
- deleteLocalInterfacePath,
4
3
  detectAllProviderStatuses,
5
4
  discoverAllModelsWithParameters,
5
+ requestEvents
6
+ } from "./chunk-QALGC7T7.js";
7
+ import {
8
+ DevProxy,
9
+ DevRunner,
10
+ deleteLocalInterfacePath,
11
+ detectAppConfig,
12
+ detectGitBranch,
13
+ devRequestEvents,
14
+ findDirsNeedingInstall,
6
15
  getApiBaseUrl,
7
16
  getApiKey,
8
17
  getConfigPath,
@@ -12,15 +21,21 @@ import {
12
21
  getLocalInterfacesDir,
13
22
  getSyncedModels,
14
23
  getUserId,
24
+ getWebInterfaceConfig,
25
+ getWebProjectDir,
26
+ initLoggerInteractive,
15
27
  pollDeviceAuth,
28
+ readTableSources,
16
29
  requestDeviceAuth,
17
- requestEvents,
18
30
  setApiKey,
19
31
  setLocalInterfacePath,
20
32
  setUserId,
33
+ stablePort,
21
34
  syncModels,
22
- verifyApiKey
23
- } from "./chunk-KLOTDVWL.js";
35
+ syncSchema,
36
+ verifyApiKey,
37
+ watchTableFiles
38
+ } from "./chunk-C3JPRLSS.js";
24
39
 
25
40
  // src/tui/index.tsx
26
41
  import { render } from "ink";
@@ -30,8 +45,8 @@ import { get as httpsGet } from "https";
30
45
  import { get as httpGet } from "http";
31
46
 
32
47
  // src/tui/App.tsx
33
- import { useEffect as useEffect16, useCallback as useCallback10, useState as useState16, useRef as useRef9 } from "react";
34
- import { Box as Box10, useApp, useStdout as useStdout7 } from "ink";
48
+ import { useEffect as useEffect20, useCallback as useCallback12, useState as useState21, useRef as useRef12 } from "react";
49
+ import { Box as Box14, useApp, useStdout as useStdout8 } from "ink";
35
50
 
36
51
  // src/tui/components/Header.tsx
37
52
  import { useState, useEffect, useMemo } from "react";
@@ -135,7 +150,7 @@ function Header({
135
150
  /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "MindStudio Local Tunnel" }),
136
151
  compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
137
152
  " v",
138
- "0.5.7"
153
+ "0.5.8"
139
154
  ] }),
140
155
  environment !== "prod" && /* @__PURE__ */ jsxs(Fragment, { children: [
141
156
  /* @__PURE__ */ jsx(Text, { children: " " }),
@@ -153,7 +168,7 @@ function Header({
153
168
  ] }),
154
169
  !compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
155
170
  "v",
156
- "0.5.7"
171
+ "0.5.8"
157
172
  ] })
158
173
  ] })
159
174
  ]
@@ -161,150 +176,11 @@ function Header({
161
176
  );
162
177
  }
163
178
 
164
- // src/tui/components/NavigationMenu.tsx
165
- import { useState as useState2, useEffect as useEffect2 } from "react";
166
- import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
167
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
168
- function NavigationMenu({
169
- items,
170
- onSelect,
171
- title
172
- }) {
173
- const { stdout } = useStdout();
174
- const compact = (stdout?.rows ?? 24) < 40;
175
- const getDefaultIndex = () => {
176
- const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
177
- return firstIdx >= 0 ? firstIdx : 0;
178
- };
179
- const [selectedIndex, setSelectedIndex] = useState2(getDefaultIndex);
180
- useEffect2(() => {
181
- setSelectedIndex(getDefaultIndex());
182
- }, [items]);
183
- const selectableItems = items.filter((i) => !i.isSeparator);
184
- const findNextEnabled = (from, direction) => {
185
- let idx = from;
186
- for (let i = 0; i < items.length; i++) {
187
- idx = (idx + direction + items.length) % items.length;
188
- if (!items[idx].disabled && !items[idx].isSeparator) return idx;
189
- }
190
- return from;
191
- };
192
- useInput((input, key) => {
193
- if (input === "q" || key.escape) {
194
- const backItem = items.find((i) => i.id === "back");
195
- if (backItem) {
196
- onSelect("back");
197
- } else if (input === "q") {
198
- onSelect("quit");
199
- }
200
- return;
201
- }
202
- if (key.upArrow || compact && key.leftArrow) {
203
- setSelectedIndex((prev) => findNextEnabled(prev, -1));
204
- } else if (key.downArrow || compact && key.rightArrow) {
205
- setSelectedIndex((prev) => findNextEnabled(prev, 1));
206
- } else if (key.return) {
207
- const item = items[selectedIndex];
208
- if (item && !item.disabled) {
209
- onSelect(item.id);
210
- }
211
- }
212
- });
213
- const hasBack = items.some((i) => i.id === "back");
214
- if (compact) {
215
- const selectedItem = items[selectedIndex];
216
- return /* @__PURE__ */ jsx2(
217
- Box2,
218
- {
219
- flexDirection: "column",
220
- paddingX: 1,
221
- borderStyle: "single",
222
- borderTop: true,
223
- borderBottom: false,
224
- borderLeft: false,
225
- borderRight: false,
226
- borderColor: "gray",
227
- children: /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
228
- if (item.isSeparator) return null;
229
- const isSelected = index === selectedIndex;
230
- return /* @__PURE__ */ jsx2(
231
- Text2,
232
- {
233
- color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
234
- bold: isSelected,
235
- wrap: "truncate-end",
236
- children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
237
- },
238
- item.id
239
- );
240
- }) })
241
- }
242
- );
243
- }
244
- const separatorExtraLines = items.filter(
245
- (item, idx) => item.isSeparator && idx > 0
246
- ).length;
247
- const menuHeight = items.length + 4 + separatorExtraLines;
248
- return /* @__PURE__ */ jsxs2(
249
- Box2,
250
- {
251
- flexDirection: "column",
252
- paddingX: 1,
253
- marginBottom: 1,
254
- borderStyle: "single",
255
- borderTop: true,
256
- borderBottom: false,
257
- borderLeft: false,
258
- borderRight: false,
259
- borderColor: "gray",
260
- children: [
261
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: title ?? "Actions" }) }),
262
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => {
263
- if (item.isSeparator) {
264
- return /* @__PURE__ */ jsx2(Box2, { marginTop: index > 0 ? 1 : 0, children: item.label ? /* @__PURE__ */ jsx2(Text2, { bold: true, color: item.color ?? "gray", wrap: "truncate-end", children: item.label }) : null }, item.id);
265
- }
266
- const isSelected = index === selectedIndex;
267
- const prefix = isSelected ? "\u276F" : " ";
268
- if (item.disabled) {
269
- return /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
270
- prefix,
271
- " ",
272
- item.label,
273
- item.disabledReason ? ` (${item.disabledReason})` : ""
274
- ] }) }, item.id);
275
- }
276
- return /* @__PURE__ */ jsxs2(Box2, { height: 1, overflow: "hidden", children: [
277
- /* @__PURE__ */ jsxs2(
278
- Text2,
279
- {
280
- color: isSelected ? "cyan" : "white",
281
- bold: isSelected,
282
- wrap: "truncate-end",
283
- children: [
284
- prefix,
285
- " ",
286
- item.label
287
- ]
288
- }
289
- ),
290
- isSelected && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
291
- " ",
292
- "- ",
293
- item.description
294
- ] })
295
- ] }, item.id);
296
- }) }),
297
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", wrap: "truncate-end", children: hasBack ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
298
- ]
299
- }
300
- );
301
- }
302
-
303
179
  // src/tui/hooks/useConnection.ts
304
- import { useState as useState3, useEffect as useEffect3, useCallback } from "react";
180
+ import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
305
181
  function useConnection() {
306
- const [status, setStatus] = useState3("connecting");
307
- const [error, setError] = useState3(null);
182
+ const [status, setStatus] = useState2("connecting");
183
+ const [error, setError] = useState2(null);
308
184
  const environment = getEnvironment();
309
185
  const connect = useCallback(async () => {
310
186
  setStatus("connecting");
@@ -327,7 +203,7 @@ function useConnection() {
327
203
  setError(err instanceof Error ? err.message : "Connection failed");
328
204
  }
329
205
  }, []);
330
- useEffect3(() => {
206
+ useEffect2(() => {
331
207
  connect();
332
208
  }, [connect]);
333
209
  return {
@@ -339,12 +215,12 @@ function useConnection() {
339
215
  }
340
216
 
341
217
  // src/tui/interfaces/hooks/useEditorSessions.ts
342
- import { useState as useState4, useEffect as useEffect4, useCallback as useCallback2, useRef } from "react";
218
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
343
219
  function useEditorSessions() {
344
- const [sessions, setSessions] = useState4([]);
345
- const [loading, setLoading] = useState4(true);
346
- const [error, setError] = useState4(null);
347
- const [refreshStatus, setRefreshStatus] = useState4("idle");
220
+ const [sessions, setSessions] = useState3([]);
221
+ const [loading, setLoading] = useState3(true);
222
+ const [error, setError] = useState3(null);
223
+ const [refreshStatus, setRefreshStatus] = useState3("idle");
348
224
  const initialLoadDone = useRef(false);
349
225
  const timerRef = useRef();
350
226
  const pollRef = useRef();
@@ -369,10 +245,10 @@ function useEditorSessions() {
369
245
  setLoading(false);
370
246
  }
371
247
  }, []);
372
- useEffect4(() => {
248
+ useEffect3(() => {
373
249
  refresh();
374
250
  }, [refresh]);
375
- useEffect4(() => {
251
+ useEffect3(() => {
376
252
  pollRef.current = setInterval(async () => {
377
253
  if (!initialLoadDone.current) return;
378
254
  try {
@@ -383,18 +259,18 @@ function useEditorSessions() {
383
259
  }, 5e3);
384
260
  return () => clearInterval(pollRef.current);
385
261
  }, []);
386
- useEffect4(() => {
262
+ useEffect3(() => {
387
263
  return () => clearTimeout(timerRef.current);
388
264
  }, []);
389
265
  return { sessions, loading, error, refreshStatus, refresh };
390
266
  }
391
267
 
392
268
  // src/tui/models/hooks/useSetupProviders.ts
393
- import { useState as useState5, useEffect as useEffect5, useCallback as useCallback3, useRef as useRef2 } from "react";
269
+ import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
394
270
  function useSetupProviders() {
395
- const [providers, setProviders] = useState5([]);
396
- const [loading, setLoading] = useState5(true);
397
- const [refreshing, setRefreshing] = useState5(false);
271
+ const [providers, setProviders] = useState4([]);
272
+ const [loading, setLoading] = useState4(true);
273
+ const [refreshing, setRefreshing] = useState4(false);
398
274
  const initialLoadDone = useRef2(false);
399
275
  const refresh = useCallback3(async () => {
400
276
  if (!initialLoadDone.current) {
@@ -408,19 +284,19 @@ function useSetupProviders() {
408
284
  setLoading(false);
409
285
  setRefreshing(false);
410
286
  }, []);
411
- useEffect5(() => {
287
+ useEffect4(() => {
412
288
  refresh();
413
289
  }, [refresh]);
414
290
  return { providers, loading, refreshing, refresh };
415
291
  }
416
292
 
417
293
  // src/tui/models/hooks/useModels.ts
418
- import { useState as useState6, useEffect as useEffect6, useCallback as useCallback4, useRef as useRef3 } from "react";
294
+ import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
419
295
  function useModels() {
420
- const [models, setModels] = useState6([]);
421
- const [warnings, setWarnings] = useState6([]);
422
- const [loading, setLoading] = useState6(true);
423
- const [refreshing, setRefreshing] = useState6(false);
296
+ const [models, setModels] = useState5([]);
297
+ const [warnings, setWarnings] = useState5([]);
298
+ const [loading, setLoading] = useState5(true);
299
+ const [refreshing, setRefreshing] = useState5(false);
424
300
  const initialLoadDone = useRef3(false);
425
301
  const refresh = useCallback4(async () => {
426
302
  if (!initialLoadDone.current) {
@@ -441,7 +317,7 @@ function useModels() {
441
317
  setRefreshing(false);
442
318
  }
443
319
  }, []);
444
- useEffect6(() => {
320
+ useEffect5(() => {
445
321
  refresh();
446
322
  }, [refresh]);
447
323
  return {
@@ -454,11 +330,11 @@ function useModels() {
454
330
  }
455
331
 
456
332
  // src/tui/models/hooks/useRequests.ts
457
- import { useState as useState7, useEffect as useEffect7, useCallback as useCallback5, useRef as useRef4 } from "react";
333
+ import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5, useRef as useRef4 } from "react";
458
334
  function useRequests(maxHistory = 50) {
459
- const [requests, setRequests] = useState7([]);
335
+ const [requests, setRequests] = useState6([]);
460
336
  const requestsRef = useRef4(/* @__PURE__ */ new Map());
461
- useEffect7(() => {
337
+ useEffect6(() => {
462
338
  const interval = setInterval(() => {
463
339
  setRequests((prev) => {
464
340
  const hasActive = prev.some((r) => r.status === "processing");
@@ -467,7 +343,7 @@ function useRequests(maxHistory = 50) {
467
343
  }, 1e3);
468
344
  return () => clearInterval(interval);
469
345
  }, []);
470
- useEffect7(() => {
346
+ useEffect6(() => {
471
347
  const unsubStart = requestEvents.onStart((event) => {
472
348
  const entry = {
473
349
  id: event.id,
@@ -532,10 +408,10 @@ function useRequests(maxHistory = 50) {
532
408
  }
533
409
 
534
410
  // src/tui/models/hooks/useRegisteredModels.ts
535
- import { useState as useState8, useEffect as useEffect8, useCallback as useCallback6 } from "react";
411
+ import { useState as useState7, useEffect as useEffect7, useCallback as useCallback6 } from "react";
536
412
  function useSyncedModels(connectionStatus) {
537
- const [syncedNames, setSyncedNames] = useState8(/* @__PURE__ */ new Set());
538
- const [syncedModels, setSyncedModels] = useState8([]);
413
+ const [syncedNames, setSyncedNames] = useState7(/* @__PURE__ */ new Set());
414
+ const [syncedModels, setSyncedModels] = useState7([]);
539
415
  const refresh = useCallback6(async () => {
540
416
  if (connectionStatus !== "connected") {
541
417
  setSyncedNames(/* @__PURE__ */ new Set());
@@ -549,7 +425,7 @@ function useSyncedModels(connectionStatus) {
549
425
  } catch {
550
426
  }
551
427
  }, [connectionStatus]);
552
- useEffect8(() => {
428
+ useEffect7(() => {
553
429
  refresh();
554
430
  }, [refresh]);
555
431
  return {
@@ -565,9 +441,9 @@ import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
565
441
  import Spinner2 from "ink-spinner";
566
442
 
567
443
  // src/tui/models/components/RequestLog.tsx
568
- import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
444
+ import { Box as Box2, Text as Text2, useStdout } from "ink";
569
445
  import Spinner from "ink-spinner";
570
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
446
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
571
447
  function formatTime(timestamp) {
572
448
  const date = new Date(timestamp);
573
449
  return date.toLocaleTimeString("en-US", {
@@ -610,28 +486,28 @@ function RequestItem({
610
486
  const elapsed = Date.now() - request.startTime;
611
487
  const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
612
488
  const stepProgress = request.step !== void 0 && request.totalSteps ? `Step ${request.step}/${request.totalSteps}` : null;
613
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
614
- /* @__PURE__ */ jsxs3(Box3, { children: [
615
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: /* @__PURE__ */ jsx3(Spinner, { type: "dots" }) }),
616
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
489
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
490
+ /* @__PURE__ */ jsxs2(Box2, { children: [
491
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
492
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
617
493
  " ",
618
494
  time,
619
495
  " "
620
496
  ] }),
621
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
622
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
623
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
624
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
497
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
498
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
499
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
500
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
625
501
  " ",
626
502
  formatDuration(elapsed),
627
503
  "..."
628
504
  ] })
629
505
  ] }),
630
- snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
506
+ snippet && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
631
507
  snippetIndent,
632
508
  snippet
633
509
  ] }),
634
- stepProgress && /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
510
+ stepProgress && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
635
511
  snippetIndent,
636
512
  stepProgress
637
513
  ] })
@@ -648,40 +524,40 @@ function RequestItem({
648
524
  resultInfo = ` \xB7 ${Math.round(request.result.videoSize / 1024 / 1024)}MB`;
649
525
  }
650
526
  const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
651
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
652
- /* @__PURE__ */ jsxs3(Box3, { children: [
653
- /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713" }),
654
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
527
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
528
+ /* @__PURE__ */ jsxs2(Box2, { children: [
529
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" }),
530
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
655
531
  " ",
656
532
  time,
657
533
  " "
658
534
  ] }),
659
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
660
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
661
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
662
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
535
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
536
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
537
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
538
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
663
539
  " ",
664
540
  duration,
665
541
  resultInfo
666
542
  ] })
667
543
  ] }),
668
- snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
544
+ snippet && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
669
545
  snippetIndent,
670
546
  snippet
671
547
  ] })
672
548
  ] });
673
549
  }
674
- return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Box3, { children: [
675
- /* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u25CF" }),
676
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
550
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Box2, { children: [
551
+ /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u25CF" }),
552
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
677
553
  " ",
678
554
  time,
679
555
  " "
680
556
  ] }),
681
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
682
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
683
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
684
- /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
557
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
558
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
559
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
560
+ /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
685
561
  " ",
686
562
  request.error || "Failed"
687
563
  ] })
@@ -692,7 +568,7 @@ function RequestLog({
692
568
  maxVisible = 8,
693
569
  hasModels = true
694
570
  }) {
695
- const { stdout } = useStdout2();
571
+ const { stdout } = useStdout();
696
572
  const width = stdout?.columns ?? 80;
697
573
  const activeRequests = requests.filter((r) => r.status === "processing");
698
574
  const completedRequests = requests.filter((r) => r.status !== "processing");
@@ -714,8 +590,8 @@ function RequestLog({
714
590
  }
715
591
  }
716
592
  const visibleRequests = [...completedToShow, ...activeRequests];
717
- return /* @__PURE__ */ jsxs3(
718
- Box3,
593
+ return /* @__PURE__ */ jsxs2(
594
+ Box2,
719
595
  {
720
596
  flexDirection: "column",
721
597
  flexGrow: 1,
@@ -723,71 +599,210 @@ function RequestLog({
723
599
  paddingX: 1,
724
600
  marginTop: 1,
725
601
  children: [
726
- /* @__PURE__ */ jsxs3(Box3, { children: [
727
- /* @__PURE__ */ jsx3(Text3, { bold: true, underline: true, color: "white", children: "Generation Requests" }),
728
- activeRequests.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
602
+ /* @__PURE__ */ jsxs2(Box2, { children: [
603
+ /* @__PURE__ */ jsx2(Text2, { bold: true, underline: true, color: "white", children: "Generation Requests" }),
604
+ activeRequests.length > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
729
605
  " (",
730
606
  activeRequests.length,
731
607
  " active)"
732
608
  ] })
733
609
  ] }),
734
- requests.length === 0 ? /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: hasModels ? "Tunnel is live \u2014 requests will appear here when models are used in MindStudio" : "Start a model to begin receiving generation requests." }) }) : /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: visibleRequests.map((request) => /* @__PURE__ */ jsx3(RequestItem, { request, width }, request.id)) })
610
+ requests.length === 0 ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: hasModels ? "Tunnel is live \u2014 requests will appear here when models are used in MindStudio" : "Start a model to begin receiving generation requests." }) }) : /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: visibleRequests.map((request) => /* @__PURE__ */ jsx2(RequestItem, { request, width }, request.id)) })
735
611
  ]
736
612
  }
737
613
  );
738
614
  }
739
615
 
740
- // src/tui/models/pages/DashboardPage.tsx
741
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
742
- function getWorkflowCount(model) {
743
- const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
744
- if (!param) return null;
745
- return param.comfyWorkflowOptions.availableWorkflows.length;
746
- }
747
- function getCapabilityLabel(capability) {
748
- switch (capability) {
749
- case "text":
750
- return { label: "Text Generation", color: "gray" };
751
- case "image":
752
- return { label: "Image Generation", color: "gray" };
753
- case "video":
754
- return { label: "Video Generation", color: "gray" };
755
- default:
756
- return { label: capability, color: "gray" };
757
- }
758
- }
759
- function DashboardPage({
760
- requests,
761
- models,
762
- modelWarnings = [],
763
- providers,
764
- providersLoading,
765
- syncedNames,
766
- modelsLoading,
767
- syncStatus = "idle",
768
- editorSessions,
769
- editorsLoading,
770
- onNavigate
616
+ // src/tui/components/NavigationMenu.tsx
617
+ import { useState as useState8, useEffect as useEffect8 } from "react";
618
+ import { Box as Box3, Text as Text3, useInput, useStdout as useStdout2 } from "ink";
619
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
620
+ function NavigationMenu({
621
+ items,
622
+ onSelect,
623
+ title
771
624
  }) {
772
- const { stdout } = useStdout3();
773
- const installedProviders = providers.filter(({ status }) => status.installed);
774
- const provNameWidth = Math.max(
775
- ...installedProviders.map((p) => p.provider.displayName.length),
776
- 8
777
- );
778
- const provStatusWidth = "Local Server Running".length;
779
- const allModelNames = new Set(models.map((m) => m.name));
780
- const unavailableSynced = [...syncedNames].filter(
781
- (name) => !allModelNames.has(name)
782
- );
783
- const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
784
- const menuItems = useMemo2(() => {
785
- return [
625
+ const { stdout } = useStdout2();
626
+ const compact = (stdout?.rows ?? 24) < 40;
627
+ const getDefaultIndex = () => {
628
+ const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
629
+ return firstIdx >= 0 ? firstIdx : 0;
630
+ };
631
+ const [selectedIndex, setSelectedIndex] = useState8(getDefaultIndex);
632
+ useEffect8(() => {
633
+ setSelectedIndex(getDefaultIndex());
634
+ }, [items]);
635
+ const selectableItems = items.filter((i) => !i.isSeparator);
636
+ const findNextEnabled = (from, direction) => {
637
+ let idx = from;
638
+ for (let i = 0; i < items.length; i++) {
639
+ idx = (idx + direction + items.length) % items.length;
640
+ if (!items[idx].disabled && !items[idx].isSeparator) return idx;
641
+ }
642
+ return from;
643
+ };
644
+ useInput((input, key) => {
645
+ if (input === "q" || key.escape) {
646
+ const backItem = items.find((i) => i.id === "back");
647
+ if (backItem) {
648
+ onSelect("back");
649
+ } else if (input === "q") {
650
+ onSelect("quit");
651
+ }
652
+ return;
653
+ }
654
+ if (key.upArrow || compact && key.leftArrow) {
655
+ setSelectedIndex((prev) => findNextEnabled(prev, -1));
656
+ } else if (key.downArrow || compact && key.rightArrow) {
657
+ setSelectedIndex((prev) => findNextEnabled(prev, 1));
658
+ } else if (key.return) {
659
+ const item = items[selectedIndex];
660
+ if (item && !item.disabled) {
661
+ onSelect(item.id);
662
+ }
663
+ }
664
+ });
665
+ const hasBack = items.some((i) => i.id === "back");
666
+ if (compact) {
667
+ const selectedItem = items[selectedIndex];
668
+ return /* @__PURE__ */ jsx3(
669
+ Box3,
786
670
  {
787
- id: "interfaces",
788
- label: "Connect to Agent",
789
- description: "Connect your local editor to a MindStudio interface or script"
790
- },
671
+ flexDirection: "column",
672
+ paddingX: 1,
673
+ borderStyle: "single",
674
+ borderTop: true,
675
+ borderBottom: false,
676
+ borderLeft: false,
677
+ borderRight: false,
678
+ borderColor: "gray",
679
+ children: /* @__PURE__ */ jsx3(Box3, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
680
+ if (item.isSeparator) return null;
681
+ const isSelected = index === selectedIndex;
682
+ return /* @__PURE__ */ jsx3(
683
+ Text3,
684
+ {
685
+ color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
686
+ bold: isSelected,
687
+ wrap: "truncate-end",
688
+ children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
689
+ },
690
+ item.id
691
+ );
692
+ }) })
693
+ }
694
+ );
695
+ }
696
+ const separatorExtraLines = items.filter(
697
+ (item, idx) => item.isSeparator && idx > 0
698
+ ).length;
699
+ const menuHeight = items.length + 4 + separatorExtraLines;
700
+ return /* @__PURE__ */ jsxs3(
701
+ Box3,
702
+ {
703
+ flexDirection: "column",
704
+ paddingX: 1,
705
+ marginBottom: 1,
706
+ borderStyle: "single",
707
+ borderTop: true,
708
+ borderBottom: false,
709
+ borderLeft: false,
710
+ borderRight: false,
711
+ borderColor: "gray",
712
+ children: [
713
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: title ?? "Actions" }) }),
714
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: items.map((item, index) => {
715
+ if (item.isSeparator) {
716
+ return /* @__PURE__ */ jsx3(Box3, { marginTop: index > 0 ? 1 : 0, children: item.label ? /* @__PURE__ */ jsx3(Text3, { bold: true, color: item.color ?? "gray", wrap: "truncate-end", children: item.label }) : null }, item.id);
717
+ }
718
+ const isSelected = index === selectedIndex;
719
+ const prefix = isSelected ? "\u276F" : " ";
720
+ if (item.disabled) {
721
+ return /* @__PURE__ */ jsx3(Box3, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
722
+ prefix,
723
+ " ",
724
+ item.label,
725
+ item.disabledReason ? ` (${item.disabledReason})` : ""
726
+ ] }) }, item.id);
727
+ }
728
+ return /* @__PURE__ */ jsxs3(Box3, { height: 1, overflow: "hidden", children: [
729
+ /* @__PURE__ */ jsxs3(
730
+ Text3,
731
+ {
732
+ color: isSelected ? "cyan" : "white",
733
+ bold: isSelected,
734
+ wrap: "truncate-end",
735
+ children: [
736
+ prefix,
737
+ " ",
738
+ item.label
739
+ ]
740
+ }
741
+ ),
742
+ isSelected && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
743
+ " ",
744
+ "- ",
745
+ item.description
746
+ ] })
747
+ ] }, item.id);
748
+ }) }),
749
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "gray", wrap: "truncate-end", children: hasBack ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
750
+ ]
751
+ }
752
+ );
753
+ }
754
+
755
+ // src/tui/models/pages/DashboardPage.tsx
756
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
757
+ function getWorkflowCount(model) {
758
+ const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
759
+ if (!param) return null;
760
+ return param.comfyWorkflowOptions.availableWorkflows.length;
761
+ }
762
+ function getCapabilityLabel(capability) {
763
+ switch (capability) {
764
+ case "text":
765
+ return { label: "Text Generation", color: "gray" };
766
+ case "image":
767
+ return { label: "Image Generation", color: "gray" };
768
+ case "video":
769
+ return { label: "Video Generation", color: "gray" };
770
+ default:
771
+ return { label: capability, color: "gray" };
772
+ }
773
+ }
774
+ function DashboardPage({
775
+ requests,
776
+ models,
777
+ modelWarnings = [],
778
+ providers,
779
+ providersLoading,
780
+ syncedNames,
781
+ modelsLoading,
782
+ syncStatus = "idle",
783
+ editorSessions,
784
+ editorsLoading,
785
+ onNavigate
786
+ }) {
787
+ const { stdout } = useStdout3();
788
+ const installedProviders = providers.filter(({ status }) => status.installed);
789
+ const provNameWidth = Math.max(
790
+ ...installedProviders.map((p) => p.provider.displayName.length),
791
+ 8
792
+ );
793
+ const provStatusWidth = "Local Server Running".length;
794
+ const allModelNames = new Set(models.map((m) => m.name));
795
+ const unavailableSynced = [...syncedNames].filter(
796
+ (name) => !allModelNames.has(name)
797
+ );
798
+ const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
799
+ const menuItems = useMemo2(() => {
800
+ return [
801
+ {
802
+ id: "interfaces",
803
+ label: "Connect to Agent",
804
+ description: "Connect your local editor to a MindStudio interface or script"
805
+ },
791
806
  {
792
807
  id: "refresh",
793
808
  label: "Sync Models",
@@ -2442,16 +2457,1142 @@ function OnboardingPage({ onComplete }) {
2442
2457
  ] });
2443
2458
  }
2444
2459
 
2460
+ // src/tui/dev/pages/DevPage.tsx
2461
+ import { useState as useState20, useEffect as useEffect19, useMemo as useMemo8 } from "react";
2462
+ import { Box as Box13, Text as Text14, useInput as useInput7 } from "ink";
2463
+ import Spinner7 from "ink-spinner";
2464
+
2465
+ // src/tui/dev/components/DevRequestLog.tsx
2466
+ import { Box as Box10, Text as Text11 } from "ink";
2467
+ import Spinner6 from "ink-spinner";
2468
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2469
+ function DevRequestLog({
2470
+ requests,
2471
+ maxVisible = 10
2472
+ }) {
2473
+ if (requests.length === 0) {
2474
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2475
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "white", underline: true, children: "Request Log" }),
2476
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Waiting for requests..." })
2477
+ ] });
2478
+ }
2479
+ const active = requests.filter((r) => r.status === "processing");
2480
+ const completed = requests.filter((r) => r.status !== "processing");
2481
+ const recent = completed.slice(-Math.max(0, maxVisible - active.length));
2482
+ const visible = [...recent, ...active].slice(-maxVisible);
2483
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2484
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "white", underline: true, children: "Request Log" }),
2485
+ visible.map((entry) => /* @__PURE__ */ jsx11(RequestEntry, { entry }, entry.id))
2486
+ ] });
2487
+ }
2488
+ function RequestEntry({ entry }) {
2489
+ const methodLabel = entry.method ?? "unknown";
2490
+ if (entry.status === "processing") {
2491
+ const elapsed = Math.round((Date.now() - entry.startTime) / 1e3);
2492
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2493
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner6, { type: "dots" }) }),
2494
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2495
+ /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
2496
+ elapsed,
2497
+ "s"
2498
+ ] })
2499
+ ] });
2500
+ }
2501
+ if (entry.status === "failed") {
2502
+ const errorLines = (entry.error ?? "Unknown error").split("\n").slice(0, 4);
2503
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2504
+ /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2505
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2716" }),
2506
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2507
+ entry.duration != null && /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
2508
+ entry.duration,
2509
+ "ms"
2510
+ ] })
2511
+ ] }),
2512
+ errorLines.map((line, i) => /* @__PURE__ */ jsxs10(Text11, { color: "red", wrap: "truncate", children: [
2513
+ " ",
2514
+ line
2515
+ ] }, i))
2516
+ ] });
2517
+ }
2518
+ const duration = entry.duration != null ? `${entry.duration}ms` : "";
2519
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2520
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
2521
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2522
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: duration })
2523
+ ] });
2524
+ }
2525
+
2526
+ // src/tui/dev/components/DevPortPrompt.tsx
2527
+ import { useState as useState16 } from "react";
2528
+ import { Box as Box11, Text as Text12 } from "ink";
2529
+ import TextInput from "ink-text-input";
2530
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2531
+ function DevPortPrompt({ onSubmit, onSkip }) {
2532
+ const [value, setValue] = useState16("");
2533
+ const [error, setError] = useState16(null);
2534
+ const handleSubmit = (input) => {
2535
+ const trimmed = input.trim();
2536
+ if (trimmed === "" || trimmed === "skip") {
2537
+ onSkip();
2538
+ return;
2539
+ }
2540
+ const port = parseInt(trimmed, 10);
2541
+ if (isNaN(port) || port < 1 || port > 65535) {
2542
+ setError('Enter a valid port number (1-65535) or "skip"');
2543
+ return;
2544
+ }
2545
+ onSubmit(port);
2546
+ };
2547
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2548
+ /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "No devPort found in your web interface config." }),
2549
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "What port is your local dev server running on?" }),
2550
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", dimColor: true, children: 'Type "skip" for backend-only mode (no frontend proxying).' }),
2551
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
2552
+ /* @__PURE__ */ jsx12(Text12, { color: "cyan", children: "Port: " }),
2553
+ /* @__PURE__ */ jsx12(
2554
+ TextInput,
2555
+ {
2556
+ value,
2557
+ onChange: (val) => {
2558
+ setValue(val);
2559
+ setError(null);
2560
+ },
2561
+ onSubmit: handleSubmit,
2562
+ placeholder: "5173"
2563
+ }
2564
+ )
2565
+ ] }),
2566
+ error && /* @__PURE__ */ jsx12(Text12, { color: "red", children: error })
2567
+ ] });
2568
+ }
2569
+
2570
+ // src/tui/dev/components/TabBar.tsx
2571
+ import { Box as Box12, Text as Text13, useStdout as useStdout7 } from "ink";
2572
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2573
+ function TabBar({ tabs, activeTab }) {
2574
+ const { stdout } = useStdout7();
2575
+ const width = stdout?.columns ?? 80;
2576
+ const tabSegments = tabs.map((tab) => {
2577
+ const isActive = tab.id === activeTab;
2578
+ const padded = ` ${tab.label} `;
2579
+ return { tab, isActive, padded };
2580
+ });
2581
+ const labelText = "Screen ";
2582
+ const hint = " \u2190/\u2192 ";
2583
+ const tabChars = tabSegments.reduce((sum, s) => sum + s.padded.length, 0);
2584
+ const remaining = Math.max(0, width - tabChars - hint.length - labelText.length);
2585
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
2586
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: labelText }),
2587
+ tabSegments.map(({ tab, isActive, padded }) => /* @__PURE__ */ jsx13(
2588
+ Text13,
2589
+ {
2590
+ bold: isActive,
2591
+ color: isActive ? "white" : "#aaaaaa",
2592
+ backgroundColor: isActive ? "blueBright" : "#333333",
2593
+ children: padded
2594
+ },
2595
+ tab.id
2596
+ )),
2597
+ /* @__PURE__ */ jsxs12(Text13, { color: "#666666", backgroundColor: "#333333", children: [
2598
+ " ".repeat(remaining),
2599
+ hint
2600
+ ] })
2601
+ ] });
2602
+ }
2603
+
2604
+ // src/tui/dev/hooks/useDevSession.ts
2605
+ import { useState as useState18, useEffect as useEffect17, useRef as useRef10, useCallback as useCallback11 } from "react";
2606
+ import { spawn as spawn3 } from "child_process";
2607
+ import { watch } from "fs";
2608
+ import { join } from "path";
2609
+
2610
+ // src/tui/dev/hooks/useDevServer.ts
2611
+ import { useState as useState17, useCallback as useCallback10, useRef as useRef9, useEffect as useEffect16 } from "react";
2612
+ import { spawn as spawn2 } from "child_process";
2613
+ var MAX_OUTPUT_LINES2 = 200;
2614
+ var PORT_READY_TIMEOUT_MS = 3e4;
2615
+ var PORT_CHECK_INTERVAL_MS = 500;
2616
+ function useDevServer() {
2617
+ const [phase, setPhase] = useState17("idle");
2618
+ const [outputLines, setOutputLines] = useState17([]);
2619
+ const [error, setError] = useState17(null);
2620
+ const processRef = useRef9(null);
2621
+ const mountedRef = useRef9(true);
2622
+ useEffect16(() => {
2623
+ mountedRef.current = true;
2624
+ return () => {
2625
+ mountedRef.current = false;
2626
+ killProcess();
2627
+ };
2628
+ }, []);
2629
+ const appendOutput = useCallback10((line) => {
2630
+ if (!mountedRef.current) return;
2631
+ setOutputLines((prev) => {
2632
+ const next = [...prev, line];
2633
+ return next.length > MAX_OUTPUT_LINES2 ? next.slice(next.length - MAX_OUTPUT_LINES2) : next;
2634
+ });
2635
+ }, []);
2636
+ const killProcess = useCallback10(() => {
2637
+ if (processRef.current) {
2638
+ processRef.current.kill("SIGTERM");
2639
+ const proc = processRef.current;
2640
+ setTimeout(() => {
2641
+ if (proc && !proc.killed) {
2642
+ proc.kill("SIGKILL");
2643
+ }
2644
+ }, 2e3);
2645
+ processRef.current = null;
2646
+ }
2647
+ }, []);
2648
+ const start = useCallback10(
2649
+ async (opts) => {
2650
+ setPhase("starting");
2651
+ setError(null);
2652
+ setOutputLines([]);
2653
+ appendOutput(`$ ${opts.command}`);
2654
+ const proc = spawn2(opts.command, [], {
2655
+ cwd: opts.cwd,
2656
+ shell: true,
2657
+ env: { ...process.env, FORCE_COLOR: "1" },
2658
+ stdio: ["ignore", "pipe", "pipe"]
2659
+ });
2660
+ processRef.current = proc;
2661
+ const handleData = (data) => {
2662
+ const lines = data.toString().split("\n");
2663
+ for (const line of lines) {
2664
+ if (line.length > 0) {
2665
+ appendOutput(line);
2666
+ }
2667
+ }
2668
+ };
2669
+ proc.stdout?.on("data", handleData);
2670
+ proc.stderr?.on("data", handleData);
2671
+ proc.on("close", (code) => {
2672
+ processRef.current = null;
2673
+ if (mountedRef.current && phase !== "idle") {
2674
+ if (code !== 0 && code !== null) {
2675
+ setError(`Dev server exited with code ${code}`);
2676
+ setPhase("error");
2677
+ } else {
2678
+ setPhase("idle");
2679
+ }
2680
+ }
2681
+ });
2682
+ proc.on("error", (err) => {
2683
+ processRef.current = null;
2684
+ if (mountedRef.current) {
2685
+ setError(err.message);
2686
+ setPhase("error");
2687
+ }
2688
+ });
2689
+ await waitForPort(opts.port, PORT_READY_TIMEOUT_MS);
2690
+ if (mountedRef.current && processRef.current) {
2691
+ setPhase("running");
2692
+ }
2693
+ },
2694
+ [appendOutput]
2695
+ );
2696
+ const stop = useCallback10(() => {
2697
+ killProcess();
2698
+ if (mountedRef.current) {
2699
+ setPhase("idle");
2700
+ }
2701
+ }, [killProcess]);
2702
+ return {
2703
+ phase,
2704
+ outputLines,
2705
+ error,
2706
+ start,
2707
+ stop
2708
+ };
2709
+ }
2710
+ async function waitForPort(port, timeoutMs) {
2711
+ const start = Date.now();
2712
+ while (Date.now() - start < timeoutMs) {
2713
+ try {
2714
+ const response = await fetch(`http://localhost:${port}/`, {
2715
+ signal: AbortSignal.timeout(1e3)
2716
+ });
2717
+ return;
2718
+ } catch {
2719
+ }
2720
+ await new Promise((resolve) => setTimeout(resolve, PORT_CHECK_INTERVAL_MS));
2721
+ }
2722
+ }
2723
+
2724
+ // src/tui/dev/hooks/useDevSession.ts
2725
+ function runNpmInstall(cwd) {
2726
+ return new Promise((resolve, reject) => {
2727
+ const proc = spawn3("npm", ["install"], {
2728
+ cwd,
2729
+ shell: true,
2730
+ stdio: ["ignore", "ignore", "pipe"]
2731
+ });
2732
+ let stderr = "";
2733
+ proc.stderr?.on("data", (chunk) => {
2734
+ stderr += chunk.toString();
2735
+ });
2736
+ proc.on("close", (code) => {
2737
+ if (code === 0) resolve();
2738
+ else reject(new Error(`npm install failed in ${cwd}: ${stderr.slice(-200)}`));
2739
+ });
2740
+ proc.on("error", reject);
2741
+ });
2742
+ }
2743
+ function useDevSession(appConfig) {
2744
+ const logInitRef = useRef10(false);
2745
+ if (!logInitRef.current) {
2746
+ initLoggerInteractive("error");
2747
+ logInitRef.current = true;
2748
+ }
2749
+ const [phase, setPhase] = useState18("detecting");
2750
+ const [session, setSession] = useState18(null);
2751
+ const [error, setError] = useState18(null);
2752
+ const [devPort, setDevPort] = useState18(null);
2753
+ const [proxyPort, setProxyPort] = useState18(null);
2754
+ const [webConfig, setWebConfig] = useState18(null);
2755
+ const [syncResult, setSyncResult] = useState18(null);
2756
+ const [scenarioResult, setScenarioResult] = useState18(null);
2757
+ const [roleOverride, setRoleOverride] = useState18(null);
2758
+ const [installStatus, setInstallStatus] = useState18(null);
2759
+ const [authRefreshUrl, setAuthRefreshUrl] = useState18(null);
2760
+ const runnerRef = useRef10(null);
2761
+ const proxyRef = useRef10(null);
2762
+ const tableWatcherCleanupRef = useRef10(() => {
2763
+ });
2764
+ const mountedRef = useRef10(true);
2765
+ const devServer = useDevServer();
2766
+ const cleanupTableWatchers = useCallback11(() => {
2767
+ tableWatcherCleanupRef.current();
2768
+ tableWatcherCleanupRef.current = () => {
2769
+ };
2770
+ }, []);
2771
+ const setupTableWatchers = useCallback11((config) => {
2772
+ cleanupTableWatchers();
2773
+ tableWatcherCleanupRef.current = watchTableFiles(
2774
+ config.tables,
2775
+ process.cwd(),
2776
+ () => {
2777
+ resyncRef.current?.();
2778
+ }
2779
+ );
2780
+ }, [cleanupTableWatchers]);
2781
+ useEffect17(() => {
2782
+ const config = getWebInterfaceConfig(appConfig);
2783
+ setWebConfig(config);
2784
+ if (config?.devPort) {
2785
+ setDevPort(config.devPort);
2786
+ setPhase("ready");
2787
+ } else if (!appConfig.interfaces.some(
2788
+ (i) => i.type === "web" && i.enabled !== false
2789
+ )) {
2790
+ setDevPort(null);
2791
+ setPhase("ready");
2792
+ } else {
2793
+ setPhase("needs_port");
2794
+ }
2795
+ }, [appConfig]);
2796
+ useEffect17(() => {
2797
+ const unsubExpired = devRequestEvents.onSessionExpired(() => {
2798
+ if (mountedRef.current) {
2799
+ setPhase("expired");
2800
+ setAuthRefreshUrl(null);
2801
+ runnerRef.current = null;
2802
+ }
2803
+ });
2804
+ const unsubImpersonate = devRequestEvents.onImpersonate((event) => {
2805
+ if (mountedRef.current) {
2806
+ setRoleOverride(event.roles);
2807
+ }
2808
+ });
2809
+ const unsubAuthStart = devRequestEvents.onAuthRefreshStart((url) => {
2810
+ if (mountedRef.current) {
2811
+ setAuthRefreshUrl(url);
2812
+ }
2813
+ });
2814
+ const unsubAuthSuccess = devRequestEvents.onAuthRefreshSuccess(() => {
2815
+ if (mountedRef.current) {
2816
+ setAuthRefreshUrl(null);
2817
+ }
2818
+ });
2819
+ const unsubAuthFailed = devRequestEvents.onAuthRefreshFailed(() => {
2820
+ if (mountedRef.current) {
2821
+ setAuthRefreshUrl(null);
2822
+ }
2823
+ });
2824
+ return () => {
2825
+ unsubExpired();
2826
+ unsubImpersonate();
2827
+ unsubAuthStart();
2828
+ unsubAuthSuccess();
2829
+ unsubAuthFailed();
2830
+ };
2831
+ }, []);
2832
+ const restartTimerRef = useRef10(void 0);
2833
+ useEffect17(() => {
2834
+ let watcher;
2835
+ try {
2836
+ const configPath = join(process.cwd(), "mindstudio.json");
2837
+ watcher = watch(configPath, () => {
2838
+ clearTimeout(restartTimerRef.current);
2839
+ restartTimerRef.current = setTimeout(async () => {
2840
+ if (!mountedRef.current || phase !== "running") return;
2841
+ cleanupTableWatchers();
2842
+ proxyRef.current?.stop();
2843
+ proxyRef.current = null;
2844
+ devServer.stop();
2845
+ if (runnerRef.current) {
2846
+ await runnerRef.current.stop().catch(() => {
2847
+ });
2848
+ runnerRef.current = null;
2849
+ }
2850
+ if (mountedRef.current) {
2851
+ setSession(null);
2852
+ setProxyPort(null);
2853
+ setSyncResult(null);
2854
+ setPhase("ready");
2855
+ }
2856
+ }, 500);
2857
+ });
2858
+ } catch {
2859
+ }
2860
+ return () => {
2861
+ clearTimeout(restartTimerRef.current);
2862
+ watcher?.close();
2863
+ };
2864
+ }, [phase, devServer]);
2865
+ useEffect17(() => {
2866
+ mountedRef.current = true;
2867
+ return () => {
2868
+ mountedRef.current = false;
2869
+ cleanupTableWatchers();
2870
+ runnerRef.current?.stop().catch(() => {
2871
+ });
2872
+ proxyRef.current?.stop();
2873
+ devServer.stop();
2874
+ };
2875
+ }, []);
2876
+ const start = useCallback11(
2877
+ async (port) => {
2878
+ const currentConfig = detectAppConfig() ?? appConfig;
2879
+ const actualPort = port ?? devPort;
2880
+ if (!currentConfig.appId) {
2881
+ setPhase("error");
2882
+ return;
2883
+ }
2884
+ if (actualPort !== void 0 && actualPort !== null) {
2885
+ setDevPort(actualPort);
2886
+ }
2887
+ setPhase("starting");
2888
+ setError(null);
2889
+ try {
2890
+ const dirsToInstall = findDirsNeedingInstall(currentConfig);
2891
+ for (const dir of dirsToInstall) {
2892
+ const dirName = dir.split("/").slice(-2).join("/");
2893
+ if (mountedRef.current) {
2894
+ setInstallStatus(`Installing dependencies in ${dirName}...`);
2895
+ }
2896
+ await runNpmInstall(dir);
2897
+ }
2898
+ if (mountedRef.current) {
2899
+ setInstallStatus(null);
2900
+ }
2901
+ if (actualPort != null) {
2902
+ const webProjectDir = getWebProjectDir(currentConfig);
2903
+ if (webProjectDir) {
2904
+ const devCommand = webConfig?.devCommand ?? "npm run dev";
2905
+ await devServer.start({
2906
+ command: devCommand,
2907
+ cwd: webProjectDir,
2908
+ port: actualPort
2909
+ });
2910
+ }
2911
+ }
2912
+ const branch = detectGitBranch();
2913
+ const proxyUrl = actualPort != null ? `http://localhost:${stablePort(currentConfig.appId)}` : void 0;
2914
+ const runner = new DevRunner(
2915
+ currentConfig.appId,
2916
+ process.cwd(),
2917
+ {
2918
+ branch,
2919
+ proxyUrl,
2920
+ methods: currentConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path }))
2921
+ }
2922
+ );
2923
+ runnerRef.current = runner;
2924
+ const devSession = await runner.start();
2925
+ if (currentConfig.tables.length > 0) {
2926
+ try {
2927
+ const tableSources = readTableSources(currentConfig);
2928
+ if (tableSources.length > 0) {
2929
+ const result = await syncSchema(
2930
+ currentConfig.appId,
2931
+ devSession.sessionId,
2932
+ tableSources
2933
+ );
2934
+ devSession.databases = result.databases;
2935
+ if (mountedRef.current) {
2936
+ setSyncResult(result);
2937
+ }
2938
+ }
2939
+ } catch {
2940
+ }
2941
+ }
2942
+ if (actualPort != null && devSession.clientContext) {
2943
+ const proxy = new DevProxy(actualPort, devSession.clientContext);
2944
+ const preferredProxyPort = stablePort(currentConfig.appId);
2945
+ const pPort = await proxy.start(preferredProxyPort);
2946
+ proxyRef.current = proxy;
2947
+ runner.setProxyUrl(`http://localhost:${pPort}`);
2948
+ runner.setProxy(proxy);
2949
+ if (mountedRef.current) {
2950
+ setProxyPort(pPort);
2951
+ }
2952
+ }
2953
+ setupTableWatchers(currentConfig);
2954
+ if (mountedRef.current) {
2955
+ setSession(devSession);
2956
+ setPhase("running");
2957
+ }
2958
+ } catch (err) {
2959
+ if (mountedRef.current) {
2960
+ setError(
2961
+ err instanceof Error ? err.message : "Failed to start dev session"
2962
+ );
2963
+ setPhase("error");
2964
+ }
2965
+ }
2966
+ },
2967
+ [appConfig, devPort, webConfig, devServer, setupTableWatchers]
2968
+ );
2969
+ const stop = useCallback11(async () => {
2970
+ cleanupTableWatchers();
2971
+ proxyRef.current?.stop();
2972
+ proxyRef.current = null;
2973
+ devServer.stop();
2974
+ if (runnerRef.current) {
2975
+ await runnerRef.current.stop().catch(() => {
2976
+ });
2977
+ runnerRef.current = null;
2978
+ }
2979
+ if (mountedRef.current) {
2980
+ setSession(null);
2981
+ setProxyPort(null);
2982
+ setPhase("stopped");
2983
+ }
2984
+ }, [devServer, cleanupTableWatchers]);
2985
+ const resyncRef = useRef10();
2986
+ const resync = useCallback11(async () => {
2987
+ if (!session) return;
2988
+ const freshConfig = detectAppConfig() ?? appConfig;
2989
+ if (!freshConfig.appId) return;
2990
+ try {
2991
+ const tableSources = readTableSources(freshConfig);
2992
+ if (tableSources.length === 0) return;
2993
+ const result = await syncSchema(
2994
+ freshConfig.appId,
2995
+ session.sessionId,
2996
+ tableSources
2997
+ );
2998
+ if (mountedRef.current) {
2999
+ setSyncResult(result);
3000
+ setSession(
3001
+ (prev) => prev ? { ...prev, databases: result.databases } : prev
3002
+ );
3003
+ }
3004
+ } catch (err) {
3005
+ if (mountedRef.current) {
3006
+ setSyncResult({
3007
+ created: [],
3008
+ altered: [],
3009
+ errors: [err instanceof Error ? err.message : "Sync failed"],
3010
+ databases: session.databases
3011
+ });
3012
+ }
3013
+ }
3014
+ }, [appConfig, session]);
3015
+ resyncRef.current = resync;
3016
+ const setImpersonation = useCallback11(async (roles) => {
3017
+ if (!runnerRef.current) return;
3018
+ await runnerRef.current.setImpersonation(roles);
3019
+ }, []);
3020
+ const clearImpersonation = useCallback11(async () => {
3021
+ if (!runnerRef.current) return;
3022
+ await runnerRef.current.clearImpersonation();
3023
+ }, []);
3024
+ const runScenario = useCallback11(async (scenarioId) => {
3025
+ if (!runnerRef.current) return;
3026
+ const freshConfig = detectAppConfig() ?? appConfig;
3027
+ const scenario = freshConfig.scenarios.find((s) => s.id === scenarioId);
3028
+ if (!scenario) return;
3029
+ const result = await runnerRef.current.runScenario(scenario);
3030
+ if (mountedRef.current) {
3031
+ setSession(
3032
+ (prev) => prev ? { ...prev, databases: result.databases } : prev
3033
+ );
3034
+ setScenarioResult({
3035
+ id: scenario.id,
3036
+ name: scenario.name,
3037
+ success: result.success,
3038
+ duration: 0,
3039
+ // filled by event listener if needed
3040
+ roles: scenario.roles,
3041
+ error: result.error
3042
+ });
3043
+ }
3044
+ }, [appConfig]);
3045
+ const submitPort = useCallback11(
3046
+ (port) => {
3047
+ setDevPort(port);
3048
+ setPhase("ready");
3049
+ },
3050
+ []
3051
+ );
3052
+ const skipFrontend = useCallback11(() => {
3053
+ setDevPort(null);
3054
+ setPhase("ready");
3055
+ }, []);
3056
+ return {
3057
+ phase,
3058
+ session,
3059
+ error,
3060
+ devPort,
3061
+ proxyPort,
3062
+ webConfig,
3063
+ devServer,
3064
+ syncResult,
3065
+ scenarioResult,
3066
+ roleOverride,
3067
+ installStatus,
3068
+ authRefreshUrl,
3069
+ start,
3070
+ stop,
3071
+ resync,
3072
+ runScenario,
3073
+ setImpersonation,
3074
+ clearImpersonation,
3075
+ submitPort,
3076
+ skipFrontend
3077
+ };
3078
+ }
3079
+
3080
+ // src/tui/dev/hooks/useDevRequests.ts
3081
+ import { useState as useState19, useEffect as useEffect18, useRef as useRef11 } from "react";
3082
+ var MAX_HISTORY = 50;
3083
+ function useDevRequests() {
3084
+ const requestsRef = useRef11(/* @__PURE__ */ new Map());
3085
+ const [requests, setRequests] = useState19([]);
3086
+ useEffect18(() => {
3087
+ const unsubStart = devRequestEvents.onStart((event) => {
3088
+ const entry = {
3089
+ id: event.id,
3090
+ type: event.type,
3091
+ method: event.method,
3092
+ status: "processing",
3093
+ startTime: event.timestamp
3094
+ };
3095
+ requestsRef.current.set(event.id, entry);
3096
+ setRequests(
3097
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3098
+ );
3099
+ });
3100
+ const unsubComplete = devRequestEvents.onComplete((event) => {
3101
+ const existing = requestsRef.current.get(event.id);
3102
+ if (existing) {
3103
+ existing.status = event.success ? "completed" : "failed";
3104
+ existing.endTime = existing.startTime + event.duration;
3105
+ existing.duration = event.duration;
3106
+ existing.error = event.error;
3107
+ setRequests(
3108
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3109
+ );
3110
+ }
3111
+ });
3112
+ return () => {
3113
+ unsubStart();
3114
+ unsubComplete();
3115
+ };
3116
+ }, []);
3117
+ useEffect18(() => {
3118
+ const hasActive = requests.some((r) => r.status === "processing");
3119
+ if (!hasActive) return;
3120
+ const interval = setInterval(() => {
3121
+ setRequests(
3122
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3123
+ );
3124
+ }, 1e3);
3125
+ return () => clearInterval(interval);
3126
+ }, [requests]);
3127
+ const activeCount = requests.filter(
3128
+ (r) => r.status === "processing"
3129
+ ).length;
3130
+ return { requests, activeCount };
3131
+ }
3132
+
3133
+ // src/tui/dev/pages/DevPage.tsx
3134
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3135
+ var TABS = [
3136
+ { id: "info", label: "Info" },
3137
+ { id: "requests", label: "Requests" },
3138
+ { id: "methods", label: "Methods" },
3139
+ { id: "server", label: "Dev Server" }
3140
+ ];
3141
+ function DevPage({ appConfig, onNavigate, termHeight }) {
3142
+ const {
3143
+ phase,
3144
+ session,
3145
+ error,
3146
+ devPort,
3147
+ proxyPort,
3148
+ devServer,
3149
+ syncResult,
3150
+ scenarioResult,
3151
+ roleOverride,
3152
+ installStatus,
3153
+ start,
3154
+ stop,
3155
+ resync,
3156
+ runScenario,
3157
+ setImpersonation,
3158
+ clearImpersonation,
3159
+ submitPort,
3160
+ skipFrontend
3161
+ } = useDevSession(appConfig);
3162
+ const { requests, activeCount } = useDevRequests();
3163
+ const [activeTab, setActiveTab] = useState20("info");
3164
+ const [showScenarioPicker, setShowScenarioPicker] = useState20(false);
3165
+ const [showRolePicker, setShowRolePicker] = useState20(false);
3166
+ const hasScenarios = appConfig.scenarios.length > 0;
3167
+ const hasRoles = appConfig.roles.length > 0;
3168
+ const runningMenuItems = useMemo8(
3169
+ () => [
3170
+ ...hasScenarios ? [{ id: "scenario", label: "Run Scenario", description: "Seed database with test data" }] : [],
3171
+ ...hasRoles ? [{ id: "impersonate", label: "Impersonate", description: roleOverride ? `Active: ${roleOverride.join(", ")}` : "Test as a different role" }] : [],
3172
+ { id: "sync", label: "Sync Schema", description: "Re-sync table definitions from disk" },
3173
+ { id: "stop", label: "Stop Session", description: "Stop the dev session and clean up" },
3174
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3175
+ { id: "quit", label: "Quit", description: "Exit the application" }
3176
+ ],
3177
+ [hasScenarios, hasRoles, roleOverride]
3178
+ );
3179
+ useInput7((_input, key) => {
3180
+ if (phase !== "running") return;
3181
+ if (key.leftArrow) {
3182
+ const idx = TABS.findIndex((t) => t.id === activeTab);
3183
+ setActiveTab(TABS[(idx - 1 + TABS.length) % TABS.length].id);
3184
+ } else if (key.rightArrow) {
3185
+ const idx = TABS.findIndex((t) => t.id === activeTab);
3186
+ setActiveTab(TABS[(idx + 1) % TABS.length].id);
3187
+ }
3188
+ });
3189
+ useEffect19(() => {
3190
+ if (phase === "ready") {
3191
+ start();
3192
+ }
3193
+ }, [phase, start]);
3194
+ if (phase === "detecting") {
3195
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
3196
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3197
+ " Checking app configuration..."
3198
+ ] }) });
3199
+ }
3200
+ if (phase === "needs_port") {
3201
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3202
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3203
+ /* @__PURE__ */ jsx14(DevPortPrompt, { onSubmit: submitPort, onSkip: skipFrontend })
3204
+ ] });
3205
+ }
3206
+ if (phase === "starting") {
3207
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3208
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3209
+ /* @__PURE__ */ jsxs13(Box13, { paddingX: 1, marginTop: 1, flexDirection: "column", children: [
3210
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3211
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3212
+ " Starting dev session..."
3213
+ ] }),
3214
+ installStatus && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3215
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3216
+ " ",
3217
+ installStatus
3218
+ ] }),
3219
+ devServer.phase === "starting" && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3220
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3221
+ " Waiting for dev server on port ",
3222
+ devPort,
3223
+ "..."
3224
+ ] }),
3225
+ devServer.outputLines.slice(-6).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, wrap: "truncate", children: line }, i))
3226
+ ] })
3227
+ ] });
3228
+ }
3229
+ if (phase === "error") {
3230
+ const errorMenuItems = [
3231
+ { id: "retry", label: "Retry", description: "Try starting the session again" },
3232
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3233
+ { id: "quit", label: "Quit", description: "Exit the application" }
3234
+ ];
3235
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3236
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3237
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3238
+ error && /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3239
+ "\u2716 ",
3240
+ error
3241
+ ] }),
3242
+ devServer.error && /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3243
+ "Dev server: ",
3244
+ devServer.error
3245
+ ] }),
3246
+ devServer.outputLines.slice(-5).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, wrap: "truncate", children: line }, i))
3247
+ ] }),
3248
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3249
+ /* @__PURE__ */ jsx14(
3250
+ NavigationMenu,
3251
+ {
3252
+ items: errorMenuItems,
3253
+ onSelect: (id) => {
3254
+ if (id === "retry") start();
3255
+ else onNavigate(id);
3256
+ }
3257
+ }
3258
+ )
3259
+ ] });
3260
+ }
3261
+ if (phase === "stopped") {
3262
+ const stoppedMenuItems = [
3263
+ { id: "restart", label: "Restart", description: "Start a new dev session" },
3264
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3265
+ { id: "quit", label: "Quit", description: "Exit the application" }
3266
+ ];
3267
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3268
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3269
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "Session stopped." }) }),
3270
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3271
+ /* @__PURE__ */ jsx14(
3272
+ NavigationMenu,
3273
+ {
3274
+ items: stoppedMenuItems,
3275
+ onSelect: (id) => {
3276
+ if (id === "restart") start();
3277
+ else onNavigate(id);
3278
+ }
3279
+ }
3280
+ )
3281
+ ] });
3282
+ }
3283
+ if (phase === "expired") {
3284
+ const expiredMenuItems = [
3285
+ { id: "restart", label: "Restart", description: "Start a new dev session" },
3286
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3287
+ { id: "quit", label: "Quit", description: "Exit the application" }
3288
+ ];
3289
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3290
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3291
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "\u26A0 Dev session expired. The platform may have timed it out." }) }),
3292
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3293
+ /* @__PURE__ */ jsx14(
3294
+ NavigationMenu,
3295
+ {
3296
+ items: expiredMenuItems,
3297
+ onSelect: (id) => {
3298
+ if (id === "restart") start();
3299
+ else onNavigate(id);
3300
+ }
3301
+ }
3302
+ )
3303
+ ] });
3304
+ }
3305
+ const statusLines = 4;
3306
+ const menuLines = 8;
3307
+ const contentHeight = Math.max(5, (termHeight ?? 30) - statusLines - menuLines);
3308
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3309
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs13(Box13, { gap: 2, children: [
3310
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", children: appConfig.name }),
3311
+ /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3312
+ "\u25CF ",
3313
+ session?.branch ?? "main"
3314
+ ] }),
3315
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: session?.previewUrl ?? session?.webInterfaceUrl ?? "" })
3316
+ ] }) }),
3317
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
3318
+ activeTab === "info" && /* @__PURE__ */ jsx14(
3319
+ InfoTab,
3320
+ {
3321
+ appConfig,
3322
+ session,
3323
+ devPort,
3324
+ proxyPort,
3325
+ devServerPhase: devServer.phase,
3326
+ requests,
3327
+ syncResult,
3328
+ scenarioResult,
3329
+ roleOverride,
3330
+ contentHeight
3331
+ }
3332
+ ),
3333
+ activeTab === "requests" && /* @__PURE__ */ jsx14(DevRequestLog, { requests, maxVisible: contentHeight }),
3334
+ activeTab === "methods" && /* @__PURE__ */ jsx14(MethodsTab, { appConfig, contentHeight }),
3335
+ activeTab === "server" && /* @__PURE__ */ jsx14(
3336
+ DevServerTab,
3337
+ {
3338
+ devPort,
3339
+ phase: devServer.phase,
3340
+ outputLines: devServer.outputLines,
3341
+ error: devServer.error,
3342
+ contentHeight
3343
+ }
3344
+ )
3345
+ ] }),
3346
+ showScenarioPicker ? /* @__PURE__ */ jsx14(
3347
+ NavigationMenu,
3348
+ {
3349
+ title: "Select Scenario",
3350
+ items: [
3351
+ ...appConfig.scenarios.map((s) => ({
3352
+ id: `scenario:${s.id}`,
3353
+ label: s.name ?? s.id,
3354
+ description: s.description ?? `Roles: ${s.roles.length > 0 ? s.roles.join(", ") : "none"}`
3355
+ })),
3356
+ { id: "back", label: "Back", description: "Return to actions" }
3357
+ ],
3358
+ onSelect: (id) => {
3359
+ setShowScenarioPicker(false);
3360
+ if (id.startsWith("scenario:")) {
3361
+ runScenario(id.slice("scenario:".length));
3362
+ }
3363
+ }
3364
+ }
3365
+ ) : showRolePicker ? /* @__PURE__ */ jsx14(
3366
+ NavigationMenu,
3367
+ {
3368
+ title: "Impersonate Role",
3369
+ items: [
3370
+ ...appConfig.roles.map((r) => ({
3371
+ id: `role:${r.id}`,
3372
+ label: r.name ?? r.id,
3373
+ description: r.description ?? r.id
3374
+ })),
3375
+ ...roleOverride ? [{ id: "clear", label: "Clear Override", description: "Revert to default session roles" }] : [],
3376
+ { id: "back", label: "Back", description: "Return to actions" }
3377
+ ],
3378
+ onSelect: (id) => {
3379
+ setShowRolePicker(false);
3380
+ if (id === "clear") {
3381
+ clearImpersonation();
3382
+ } else if (id.startsWith("role:")) {
3383
+ setImpersonation([id.slice("role:".length)]);
3384
+ }
3385
+ }
3386
+ }
3387
+ ) : /* @__PURE__ */ jsx14(
3388
+ NavigationMenu,
3389
+ {
3390
+ items: runningMenuItems,
3391
+ onSelect: (id) => {
3392
+ if (id === "scenario") setShowScenarioPicker(true);
3393
+ else if (id === "impersonate") setShowRolePicker(true);
3394
+ else if (id === "sync") resync();
3395
+ else if (id === "stop") stop();
3396
+ else onNavigate(id);
3397
+ }
3398
+ }
3399
+ ),
3400
+ /* @__PURE__ */ jsx14(TabBar, { tabs: TABS, activeTab })
3401
+ ] });
3402
+ }
3403
+ function AppInfoHeader({ appConfig }) {
3404
+ return /* @__PURE__ */ jsxs13(
3405
+ Box13,
3406
+ {
3407
+ flexDirection: "column",
3408
+ paddingX: 1,
3409
+ paddingY: 1,
3410
+ borderStyle: "round",
3411
+ borderColor: "gray",
3412
+ children: [
3413
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", children: appConfig.name }),
3414
+ appConfig.description && /* @__PURE__ */ jsx14(Text14, { color: "gray", children: appConfig.description }),
3415
+ !appConfig.appId && /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: '\u26A0 No "appId" field in mindstudio.json \u2014 add your app ID to start a dev session' })
3416
+ ]
3417
+ }
3418
+ );
3419
+ }
3420
+ function InfoTab({
3421
+ appConfig,
3422
+ session,
3423
+ devPort,
3424
+ proxyPort,
3425
+ devServerPhase,
3426
+ requests,
3427
+ syncResult,
3428
+ scenarioResult,
3429
+ roleOverride,
3430
+ contentHeight
3431
+ }) {
3432
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3433
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Session" }),
3434
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3435
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Session ID: " }),
3436
+ /* @__PURE__ */ jsx14(Text14, { children: session?.sessionId ?? "..." })
3437
+ ] }),
3438
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3439
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Release ID: " }),
3440
+ /* @__PURE__ */ jsx14(Text14, { children: session?.releaseId ?? "..." })
3441
+ ] }),
3442
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3443
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Branch: " }),
3444
+ /* @__PURE__ */ jsx14(Text14, { children: session?.branch ?? "..." })
3445
+ ] }),
3446
+ session?.user && /* @__PURE__ */ jsxs13(Text14, { children: [
3447
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "User: " }),
3448
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3449
+ session.user.name,
3450
+ " (",
3451
+ session.user.email,
3452
+ ")"
3453
+ ] })
3454
+ ] }),
3455
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "App URL" }) }),
3456
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", bold: true, children: session?.previewUrl ?? session?.webInterfaceUrl ?? "..." }),
3457
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Dev Server" }) }),
3458
+ devPort !== null ? /* @__PURE__ */ jsxs13(Text14, { children: [
3459
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3460
+ "localhost:",
3461
+ devPort
3462
+ ] }),
3463
+ devServerPhase === "running" ? /* @__PURE__ */ jsx14(Text14, { color: "green", children: " \u25CF running" }) : devServerPhase === "starting" ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: " \u25CB starting" }) : /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3464
+ " \u25CB ",
3465
+ devServerPhase
3466
+ ] })
3467
+ ] }) : /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Backend-only mode (no frontend)" }),
3468
+ roleOverride && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3469
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Impersonation" }),
3470
+ /* @__PURE__ */ jsxs13(Text14, { color: "yellow", children: [
3471
+ " \u25CF Active: ",
3472
+ roleOverride.join(", ")
3473
+ ] })
3474
+ ] }),
3475
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Databases" }) }),
3476
+ (session?.databases ?? []).length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No databases" }) : session?.databases.map((db) => /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3477
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3478
+ " ",
3479
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: db.name })
3480
+ ] }),
3481
+ db.tables.map((table) => /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3482
+ " ",
3483
+ table.name
3484
+ ] }, table.name))
3485
+ ] }, db.id)),
3486
+ syncResult && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3487
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Schema Sync" }),
3488
+ syncResult.created.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3489
+ " \u2713 Created: ",
3490
+ syncResult.created.join(", ")
3491
+ ] }),
3492
+ syncResult.altered.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "yellow", children: [
3493
+ " \u2713 Altered: ",
3494
+ syncResult.altered.join(", ")
3495
+ ] }),
3496
+ syncResult.errors.map((err, i) => /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3497
+ " \u2716 ",
3498
+ err
3499
+ ] }, i)),
3500
+ syncResult.created.length === 0 && syncResult.altered.length === 0 && syncResult.errors.length === 0 && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: " No changes" })
3501
+ ] }),
3502
+ scenarioResult && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3503
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Last Scenario" }),
3504
+ scenarioResult.success ? /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3505
+ ' \u2713 "',
3506
+ scenarioResult.name ?? scenarioResult.id,
3507
+ '" applied'
3508
+ ] }) : /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3509
+ ' \u2716 "',
3510
+ scenarioResult.name ?? scenarioResult.id,
3511
+ '" failed: ',
3512
+ scenarioResult.error
3513
+ ] }),
3514
+ scenarioResult.roles.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3515
+ " Roles: ",
3516
+ scenarioResult.roles.join(", ")
3517
+ ] })
3518
+ ] }),
3519
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Recent Requests" }) }),
3520
+ requests.length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No requests yet" }) : requests.slice(-5).map((req) => /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
3521
+ req.status === "completed" && /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713" }),
3522
+ req.status === "failed" && /* @__PURE__ */ jsx14(Text14, { color: "red", children: "\u2716" }),
3523
+ req.status === "processing" && /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "\u25CF" }),
3524
+ /* @__PURE__ */ jsx14(Text14, { children: req.method ?? "unknown" }),
3525
+ req.duration != null && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3526
+ req.duration,
3527
+ "ms"
3528
+ ] }),
3529
+ req.status === "failed" && req.error && /* @__PURE__ */ jsx14(Text14, { color: "red", wrap: "truncate", children: req.error.split("\n")[0] })
3530
+ ] }, req.id))
3531
+ ] });
3532
+ }
3533
+ function MethodsTab({
3534
+ appConfig,
3535
+ contentHeight
3536
+ }) {
3537
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3538
+ /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "white", underline: true, children: [
3539
+ "Methods (",
3540
+ appConfig.methods.length,
3541
+ ")"
3542
+ ] }),
3543
+ appConfig.methods.slice(0, contentHeight - 2).map((method) => /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
3544
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: method.export }),
3545
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3546
+ "\u2192 ",
3547
+ method.id
3548
+ ] }),
3549
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3550
+ "(",
3551
+ method.path,
3552
+ ")"
3553
+ ] })
3554
+ ] }, method.id)),
3555
+ appConfig.methods.length === 0 && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No methods defined" })
3556
+ ] });
3557
+ }
3558
+ function DevServerTab({
3559
+ devPort,
3560
+ phase,
3561
+ outputLines,
3562
+ error,
3563
+ contentHeight
3564
+ }) {
3565
+ const visibleLines = contentHeight - 3;
3566
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3567
+ /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "white", underline: true, children: [
3568
+ "Dev Server",
3569
+ phase === "running" ? /* @__PURE__ */ jsx14(Text14, { color: "green", children: " \u25CF running" }) : phase === "starting" ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: " \u25CB starting" }) : phase === "error" ? /* @__PURE__ */ jsx14(Text14, { color: "red", children: " \u2716 error" }) : /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3570
+ " \u25CB ",
3571
+ phase
3572
+ ] })
3573
+ ] }),
3574
+ devPort !== null && /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3575
+ "localhost:",
3576
+ devPort
3577
+ ] }),
3578
+ devPort === null && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Backend-only mode (no frontend)" }),
3579
+ error && /* @__PURE__ */ jsx14(Text14, { color: "red", children: error }),
3580
+ outputLines.slice(-visibleLines).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", wrap: "truncate", children: line }, i)),
3581
+ outputLines.length === 0 && phase !== "idle" && !error && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Waiting for output..." }),
3582
+ outputLines.length === 0 && phase === "idle" && !error && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Dev server not started" })
3583
+ ] });
3584
+ }
3585
+
2445
3586
  // src/tui/App.tsx
2446
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3587
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2447
3588
  var MODEL_TYPE_MAP = {
2448
3589
  text: "llm_chat",
2449
3590
  image: "image_generation",
2450
3591
  video: "video_generation"
2451
3592
  };
2452
- function App({ runner }) {
3593
+ function App({ runner, appConfig }) {
2453
3594
  const { exit } = useApp();
2454
- const { stdout } = useStdout7();
3595
+ const { stdout } = useStdout8();
2455
3596
  const {
2456
3597
  status: connectionStatus,
2457
3598
  environment,
@@ -2477,31 +3618,31 @@ function App({ runner }) {
2477
3618
  refresh: refreshSynced
2478
3619
  } = useSyncedModels(connectionStatus);
2479
3620
  const shouldOnboard = getApiKey() === void 0 || getUserId() === void 0;
2480
- const [page, setPage] = useState16(
2481
- shouldOnboard ? "onboarding" : "dashboard"
3621
+ const [page, setPage] = useState21(
3622
+ shouldOnboard ? "onboarding" : appConfig ? "dev" : "dashboard"
2482
3623
  );
2483
- const [syncStatus, setSyncStatus] = useState16(
3624
+ const [syncStatus, setSyncStatus] = useState21(
2484
3625
  "idle"
2485
3626
  );
2486
- const syncTimerRef = useRef9(void 0);
2487
- const lastSyncPayloadRef = useRef9("");
2488
- useEffect16(() => {
3627
+ const syncTimerRef = useRef12(void 0);
3628
+ const lastSyncPayloadRef = useRef12("");
3629
+ useEffect20(() => {
2489
3630
  if (connectionStatus === "not_authenticated") {
2490
3631
  setPage("onboarding");
2491
3632
  }
2492
3633
  }, [connectionStatus]);
2493
- useEffect16(() => {
3634
+ useEffect20(() => {
2494
3635
  if (page === "dashboard") {
2495
3636
  refreshAll();
2496
3637
  }
2497
3638
  }, [page]);
2498
- useEffect16(() => {
3639
+ useEffect20(() => {
2499
3640
  if (connectionStatus === "connected" && syncedModels.length > 0) {
2500
3641
  runner.start(syncedModels);
2501
3642
  }
2502
3643
  }, [connectionStatus, syncedModels, runner]);
2503
- useEffect16(() => () => runner.stop(), [runner]);
2504
- const refreshAll = useCallback10(
3644
+ useEffect20(() => () => runner.stop(), [runner]);
3645
+ const refreshAll = useCallback12(
2505
3646
  async (silent = false) => {
2506
3647
  if (!silent) setSyncStatus("syncing");
2507
3648
  const [discoveredModels] = await Promise.all([
@@ -2531,21 +3672,21 @@ function App({ runner }) {
2531
3672
  },
2532
3673
  [refreshProviders, refreshModels, refreshSynced]
2533
3674
  );
2534
- useEffect16(() => {
3675
+ useEffect20(() => {
2535
3676
  if (connectionStatus !== "connected" || page !== "dashboard") return;
2536
3677
  const interval = setInterval(() => refreshAll(true), 1500);
2537
3678
  return () => clearInterval(interval);
2538
3679
  }, [connectionStatus, page, refreshAll]);
2539
- const handleQuit = useCallback10(() => {
3680
+ const handleQuit = useCallback12(() => {
2540
3681
  runner.stop();
2541
3682
  exit();
2542
3683
  }, [runner, exit]);
2543
- const handleOnboardingComplete = useCallback10(() => {
3684
+ const handleOnboardingComplete = useCallback12(() => {
2544
3685
  retryConnection();
2545
3686
  refreshAll();
2546
3687
  setPage("dashboard");
2547
3688
  }, [retryConnection, refreshAll]);
2548
- const handleNavigate = useCallback10(
3689
+ const handleNavigate = useCallback12(
2549
3690
  (id) => {
2550
3691
  switch (id) {
2551
3692
  case "interfaces":
@@ -2557,6 +3698,12 @@ function App({ runner }) {
2557
3698
  case "setup":
2558
3699
  setPage("setup");
2559
3700
  break;
3701
+ case "dev":
3702
+ setPage("dev");
3703
+ break;
3704
+ case "dashboard":
3705
+ setPage("dashboard");
3706
+ break;
2560
3707
  case "refresh":
2561
3708
  refreshAll();
2562
3709
  break;
@@ -2570,7 +3717,7 @@ function App({ runner }) {
2570
3717
  const subpageMenuItems = [
2571
3718
  { id: "back", label: "Back", description: "Return to dashboard" }
2572
3719
  ];
2573
- const handleSubpageNavigate = useCallback10(
3720
+ const handleSubpageNavigate = useCallback12(
2574
3721
  (id) => {
2575
3722
  if (id === "back") {
2576
3723
  setPage("dashboard");
@@ -2580,11 +3727,11 @@ function App({ runner }) {
2580
3727
  },
2581
3728
  [handleNavigate]
2582
3729
  );
2583
- const [termSize, setTermSize] = useState16({
3730
+ const [termSize, setTermSize] = useState21({
2584
3731
  rows: stdout?.rows ?? 24,
2585
3732
  columns: stdout?.columns ?? 80
2586
3733
  });
2587
- useEffect16(() => {
3734
+ useEffect20(() => {
2588
3735
  if (!stdout) return;
2589
3736
  const onResize = () => {
2590
3737
  stdout.write("\x1B[2J\x1B[H");
@@ -2597,8 +3744,8 @@ function App({ runner }) {
2597
3744
  }, [stdout]);
2598
3745
  const termHeight = termSize.rows - 4;
2599
3746
  const compactHeader = termSize.rows <= 45 || termSize.columns <= 90;
2600
- return /* @__PURE__ */ jsx11(Box10, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx11(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
2601
- /* @__PURE__ */ jsx11(
3747
+ return /* @__PURE__ */ jsx15(Box14, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx15(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs14(Fragment5, { children: [
3748
+ /* @__PURE__ */ jsx15(
2602
3749
  Header,
2603
3750
  {
2604
3751
  connection: connectionStatus,
@@ -2609,7 +3756,7 @@ function App({ runner }) {
2609
3756
  hasActiveRequest: activeRequestCount > 0
2610
3757
  }
2611
3758
  ),
2612
- page === "dashboard" && /* @__PURE__ */ jsx11(
3759
+ page === "dashboard" && /* @__PURE__ */ jsx15(
2613
3760
  DashboardPage,
2614
3761
  {
2615
3762
  requests,
@@ -2625,8 +3772,8 @@ function App({ runner }) {
2625
3772
  onNavigate: handleNavigate
2626
3773
  }
2627
3774
  ),
2628
- page === "setup" && /* @__PURE__ */ jsx11(SetupPage, { onBack: () => setPage("dashboard") }),
2629
- page === "interfaces" && /* @__PURE__ */ jsx11(
3775
+ page === "setup" && /* @__PURE__ */ jsx15(SetupPage, { onBack: () => setPage("dashboard") }),
3776
+ page === "interfaces" && /* @__PURE__ */ jsx15(
2630
3777
  InterfacesPage,
2631
3778
  {
2632
3779
  onBack: () => setPage("dashboard"),
@@ -2635,12 +3782,12 @@ function App({ runner }) {
2635
3782
  refresh: editorSessions.refresh
2636
3783
  }
2637
3784
  ),
2638
- page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
2639
- page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(
2640
- NavigationMenu,
3785
+ page === "dev" && appConfig && /* @__PURE__ */ jsx15(
3786
+ DevPage,
2641
3787
  {
2642
- items: subpageMenuItems,
2643
- onSelect: handleSubpageNavigate
3788
+ appConfig,
3789
+ onNavigate: handleNavigate,
3790
+ termHeight
2644
3791
  }
2645
3792
  )
2646
3793
  ] }) });
@@ -2656,7 +3803,7 @@ function getInstallMethod() {
2656
3803
  return "npm";
2657
3804
  }
2658
3805
  function getCurrentVersion() {
2659
- return "0.5.7";
3806
+ return "0.5.8";
2660
3807
  }
2661
3808
  async function fetchLatestVersion() {
2662
3809
  try {
@@ -2702,15 +3849,15 @@ async function checkForUpdate() {
2702
3849
  }
2703
3850
 
2704
3851
  // src/tui/components/UpdatePrompt.tsx
2705
- import { useState as useState17, useEffect as useEffect17, useMemo as useMemo8 } from "react";
2706
- import { Box as Box11, Text as Text11, useInput as useInput7, useStdout as useStdout8 } from "ink";
3852
+ import { useState as useState22, useEffect as useEffect21, useMemo as useMemo9 } from "react";
3853
+ import { Box as Box15, Text as Text15, useInput as useInput8, useStdout as useStdout9 } from "ink";
2707
3854
  import chalk4 from "chalk";
2708
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3855
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2709
3856
  var SHIMMER_SPEED2 = 35;
2710
3857
  function useShimmerLogo2() {
2711
- const [frame, setFrame] = useState17(0);
2712
- const lines = useMemo8(() => LogoString.split("\n"), []);
2713
- const totalChars = useMemo8(() => {
3858
+ const [frame, setFrame] = useState22(0);
3859
+ const lines = useMemo9(() => LogoString.split("\n"), []);
3860
+ const totalChars = useMemo9(() => {
2714
3861
  let count = 0;
2715
3862
  for (const line of lines) {
2716
3863
  for (const ch of line) {
@@ -2720,13 +3867,13 @@ function useShimmerLogo2() {
2720
3867
  return count;
2721
3868
  }, [lines]);
2722
3869
  const cycleLength = totalChars + 40;
2723
- useEffect17(() => {
3870
+ useEffect21(() => {
2724
3871
  const interval = setInterval(() => {
2725
3872
  setFrame((f) => (f + 1) % cycleLength);
2726
3873
  }, SHIMMER_SPEED2);
2727
3874
  return () => clearInterval(interval);
2728
3875
  }, [cycleLength]);
2729
- return useMemo8(() => {
3876
+ return useMemo9(() => {
2730
3877
  const sweepPos = frame;
2731
3878
  const holdEnd = totalChars + 20;
2732
3879
  let charIdx = 0;
@@ -2769,19 +3916,19 @@ function UpdatePrompt({
2769
3916
  latestVersion,
2770
3917
  onChoice
2771
3918
  }) {
2772
- const { stdout } = useStdout8();
3919
+ const { stdout } = useStdout9();
2773
3920
  const shimmerLogo = useShimmerLogo2();
2774
- useInput7(() => {
3921
+ useInput8(() => {
2775
3922
  onChoice(true);
2776
3923
  });
2777
3924
  const termHeight = (stdout?.rows ?? 24) - 4;
2778
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", height: termHeight, children: [
2779
- /* @__PURE__ */ jsx12(Box11, { flexGrow: 1 }),
2780
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", alignItems: "center", children: [
2781
- /* @__PURE__ */ jsx12(Text11, { children: shimmerLogo }),
2782
- /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx12(Text11, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
2783
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", alignItems: "center", children: [
2784
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3925
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
3926
+ /* @__PURE__ */ jsx16(Box15, { flexGrow: 1 }),
3927
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", alignItems: "center", children: [
3928
+ /* @__PURE__ */ jsx16(Text15, { children: shimmerLogo }),
3929
+ /* @__PURE__ */ jsx16(Box15, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx16(Text15, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
3930
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", alignItems: "center", children: [
3931
+ /* @__PURE__ */ jsxs15(Text15, { color: "gray", children: [
2785
3932
  "Update required ",
2786
3933
  "\u2022",
2787
3934
  " v",
@@ -2791,19 +3938,19 @@ function UpdatePrompt({
2791
3938
  " v",
2792
3939
  latestVersion
2793
3940
  ] }),
2794
- /* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { color: "cyan", bold: true, children: "Press any key to update" }) })
3941
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { color: "cyan", bold: true, children: "Press any key to update" }) })
2795
3942
  ] })
2796
3943
  ] }),
2797
- /* @__PURE__ */ jsx12(Box11, { flexGrow: 1 })
3944
+ /* @__PURE__ */ jsx16(Box15, { flexGrow: 1 })
2798
3945
  ] });
2799
3946
  }
2800
3947
 
2801
3948
  // src/tui/index.tsx
2802
- import { jsx as jsx13 } from "react/jsx-runtime";
3949
+ import { jsx as jsx17 } from "react/jsx-runtime";
2803
3950
  async function promptForUpdate(currentVersion, latestVersion) {
2804
3951
  return new Promise((resolve) => {
2805
3952
  const { unmount } = render(
2806
- /* @__PURE__ */ jsx13(
3953
+ /* @__PURE__ */ jsx17(
2807
3954
  UpdatePrompt,
2808
3955
  {
2809
3956
  currentVersion,
@@ -2886,14 +4033,16 @@ async function startTUI() {
2886
4033
  }
2887
4034
  console.clear();
2888
4035
  }
4036
+ const appConfig = detectAppConfig(process.cwd());
2889
4037
  const runner = new TunnelRunner();
2890
- const { waitUntilExit } = render(/* @__PURE__ */ jsx13(App, { runner }), {
2891
- exitOnCtrlC: true
2892
- });
4038
+ const { waitUntilExit } = render(
4039
+ /* @__PURE__ */ jsx17(App, { runner, appConfig: appConfig ?? void 0 }),
4040
+ { exitOnCtrlC: true }
4041
+ );
2893
4042
  await waitUntilExit();
2894
4043
  runner.stop();
2895
4044
  }
2896
4045
  export {
2897
4046
  startTUI
2898
4047
  };
2899
- //# sourceMappingURL=tui-UNFZSO7R.js.map
4048
+ //# sourceMappingURL=tui-4PJCFILV.js.map